Changelog
Product and public API changes, rendered from CHANGELOG.md at the repository root.
Changelog
Mobile spacing + shell polish — 2026-10-31
- Marketing/saas shells: header/content/footer rhythm (section
py-14+, hero/footer/page header), ≥44px chrome (hamburger, locale, footer links, notification bell). - Soft elevation on hubs/home strips/auth card/consent banners; AuthWrapper + TopBar safe-area insets; ColorModeToggle coarse-pointer targets.
- Shared
MarketingPage/PageHero/PageHeroBand/ContentBand+ saasConsolePage; normalized inner-page rhythm; wider phone gutters; soft-elevation tiles; opaque sticky marketing header; AuthWrapper landmarks.
Theme + console/marketing IA polish — 2026-10-31
- Mobile/Safari: viewport-fit=cover, dvh/svh, safe-area on nav/sheets, ≥44px touch rows on mobile, autofill/tap-highlight, soft dialogs, Learn after Pricing.
- Theme: soft elevation instead of harsh borders; marketing no longer overrides tokens with ink-mono leftovers; Helvetica +
#fd366eprimary retained. - Marketing: SocialProof immediately after IntegrationsStrip; Pricing only top-level (not under Resources).
- SaaS: index nav Overview → Data → Search (Playground) → Relevance → Experience → Results; denser sidebar; API keys / AI agents in settings hub.
Appwrite pink + Helvetica theme — 2026-10-31
- UI: product font stack is Helvetica Neue / Helvetica / Arial (dropped Figtree from saas, marketing, docs).
- Theme:
@repo/tailwind-configlight/dark tokens follow Appwrite docs surfaces with brand/primary#fd366e.
Green main checks — 2026-10-30
- API changes: TypeScript SDK methods that take both a JSON body and query params now use
(…, body, query?)like PHP/Go (fixes CLIupsertDocuments(index, body)arity under type-check). - Tooling:
turbo/turbo.citype-checkdepends on^buildso@repo/sdkdist exists foraacsearch-cli; roottsconfigexcludes Bitrix/vendor JS noise for oxlint; oxfmt/oxlint ignorepackages/aacsearch-ui/vendor; design-lint:text-[11px]→text-xsin CollectionHealthChrome (budget stays 8).
Docs + marketing UX polish — 2026-10-30
- Docs: quote broken RU glossary YAML descriptions (homepage collection load); branded fumadocs
not-found; EN search/TOC i18n parity; soft-land locale switch off RU-only glossary deep links; localized Copy Markdown. - Marketing: wire home sections through
MarketingSection/SectionHeader(PR #22 patterns); no purple/glow kits; no marketing copy key changes.
Search REST explainability parity — 2026-10-30
- API changes:
POST|GET /indexes/{index}/searchintent.reasons— human-readable why each constraint/entity fired (already computed bysearchForIndex, previously stripped from REST; private-field mentions stay stripped). Ranking unchanged. - API changes: same search responses may include
constraint_diagnosticsonly whenexplain=true(zero-result hard-filter probes). Default payloads omit it.
Embed / widget gallery console UX — 2026-10-30
- Widgets gallery, Hosting install, per-widget editor, analytics and history:
EmptyStatefor loading / error / empty;HintTooltipon gallery and section titles. - Install snippets stay on
@repo/uiJsonPreview(including hosted iframe embed); no committedpublic/embedbundles. - i18n:
search.embedBuildergallery / install / analytics / history hints (en/de/es/fr/ru).
Webhooks + org members EmptyState UX — 2026-10-30
- Admin webhook deliveries:
EmptyStatefor loading / error / empty (filter clear CTA);ConsoleSectionhint; missingwebhooks.searchi18n fixed. Signing secrets are not shown. - Organization members / invitations: loading / error / empty via
EmptyState; one primary invite CTA focusing the invite form;HintTooltipon members + invite sections (en/de/es/fr/ru).
Relevance settings console chrome — 2026-10-30
- Relevance: CollectionHealthChrome + scannable AdvancedFlagsChrome for opt-in smart ranking (semantic / graph fusion / rerank / HyDE); EmptyState for loading, health error, schema-empty; HintTooltip on advanced flags — flags stay off by default.
- i18n:
search.searchSettings.advancedFlagsand related tips (en/de/es/fr/ru).
API keys / billing / usage EmptyState UX — 2026-10-30
- Console API keys, billing finance, wallet, invoices and plan usage:
EmptyStatefor loading / error / empty with one primary CTA;HintTooltipon key section titles (en/de/es/fr/ru). - Pricing table empty when no other plans; indexes plan-usage section always visible for admins with meters or unavailable EmptyState.
- No new JsonPreview surfaces (key secret remains copy-once plaintext; no JSON payloads on these pages).
Sync error recovery UX — 2026-10-30
- Sync dashboard:
EmptyStatefor loading / load error / empty runs; failed-run banner with Retry + Open logs; row and Sheet recovery actions; Schema / Documents links when the failure classifies as mapping or document issues (en/de/es/fr/ru).
Merchandising rule conflicts API — 2026-10-30
- Domain helper
detectMerchandisingConflictsinpackages/api/lib/search(overlap, redirect precedence, pin/bury/exclude compete) — no LLM. - Console oRPC
listMerchandisingRuleConflicts; Rules UI prefers the server payload and falls back to the shared client helper. - API changes:
GET /api/v1/rules/conflicts?index=returns conflict/precedence reasons for published rules (settings:write). - i18n: conflict source labels in en/de/es/fr/ru.
Playground explainability UX — 2026-10-30
- Playground “Why these results” surfaces API
understanding/guide/hitReasons/ intent / relaxation / rules as scannable sections with@repo/uiBadge, HintTooltip and EmptyState (no silent query rewrite — adopt / use-relaxed stay explicit). - Per-hit reason kind badges on result rows; explain panel lists up to five hits (kinds localized).
- i18n:
search.playground.explainSections|explainHints|reasonKinds|hitReasonsFor|hitReasonsMore|adoptQueryHint*|useRelaxedHint*(en/de/es/fr/ru).
UX wave 4 — wire drawer / carousel / menubar / navigation-menu — 2026-10-30
- Wire landed
@repo/uiprimitives into real console surfaces (not kit demos); Sidebar stays unused per ADR. - Drawer:
AdvisorDrawer+ Documents import use vaul Drawer (direction="right"); Drawer primitive supports top/bottom/left/right viadata-vaul-drawer-direction. - Carousel + Menubar: Widget gallery — mobile preview carousel; Configure / Lifecycle menubar replaces flat overflow menu.
- NavigationMenu: Admin Search ops tab strip (viewport optional for link-only strips).
- i18n:
search.embedBuilder.gallery.menuConfigure|menuLifecycle|carouselPrevious|carouselNext(en/de/es/fr/ru).
Design-lint ratchet (187 → 8) — 2026-10-30
- Lower
pnpm lint:design--max-warningsfrom 187 to 8 after clearing easyno-raw-colors/no-arbitrary-valuesfindings inapps/saas+packages/ui(no page redesign). - Map off-scale type (
text-[11px]/text-[10px]/ …) totext-xs/text-sm; status amber/emerald classes toconsole-*theme tokens; JsonPreview hues tojson-*tokens intooling/tailwind/theme.css. - Docs:
docs/development/design-lint.mdceiling kept in sync.
Console JsonPreview / EmptyState audit — 2026-10-30
- Replace raw audit/advisor/widget JSON dumps with
@repo/uiJsonPreview(admin + org audit metadata, advisor values, widget sample objects). - Replace ad-hoc blank/
<p>empty list panels withEmptyState/ConsoleEmptyacross schema, indexes, connectors, sync, facets, charts, shopping guide, notifications, and related console surfaces. - No second JSON viewers; existing copy reused (no new i18n keys).
Data-table view options + faceted filters — 2026-10-30
@repo/uidata-table: addDataTableViewOptions(column visibility) andDataTableFacetedFilter(selected-value badges + facet counts + clear facet), presentation-only on existing DropdownMenu / Command / FilterBar stack — no second kit.- IndexesTable: wire view-options + status facet counts; Documents browse source filter uses faceted filter chrome.
- i18n:
common.dataTable.viewOptions/toggleColumns/clearFacetin en/de/es/fr/ru.
Schema loading error state — 2026-10-30
- Schema fields panel no longer stays on “Loading schema…” when
getSchemaBuilderfails: show an error EmptyState with Retry (isPending || !datapreviously treated errors as loading). - Side “Default filters” card surfaces the same load failure instead of a false empty state.
Marketing section composition (Tailark / Origin / HyperUI) — 2026-10-30
- Cherry-pick landing section patterns into
apps/marketingonly: sharedMarketingSectionhelpers (SectionHeader,StatStrip,FramedBand), Origin FAQ split, HyperUI how-it-works / social-proof strips, Tailark framed final CTA, feature benefit rails. - Hero presence stays CSS keyframes (atmosphere, brand rule, rise) with
prefers-reduced-motion— nomotionnpm, no second UI kit, no purple/glow aesthetics. - Reuses existing marketing i18n (including unused
home.features.badgeas eyebrow); ink-mono theme tokens only.
@repo/ui deferred shadcn primitives — 2026-10-30
- Add remaining deferred primitives to
@repo/uionly: drawer (vaul), sidebar (+ ink-mono sidebar tokens), carousel (embla-carousel-react), menubar, navigation-menu. - Export from
packages/ui/index.ts; keep unifiedradix-ui(no per-primitive@radix-ui/react-*deps). SaaS shell not rewritten onto sidebar — wire later when a surface needs it. - Resizable left to #19 (Playground Resizable + shared Sortable) — not duplicated here.
Playground Resizable + shared Sortable — 2026-10-30
@repo/ui: shadcn-styleResizable(react-resizable-panelsv4) and thinSortablehelpers (@dnd-kit) for reusable list reorder.- Playground Search: resizable results vs facets on md+ (Tabs kept; mobile collapsible facets unchanged). Compare: resizable A/B columns on lg+.
- Rules merchandising list consumes
@repo/uiSortable; curation pin dialog reuses it for pin order when two or more pins are present (no silent query rewrite). - i18n: resize / pin-order strings in en/de/es/fr/ru.
Post-UX-merge type cleanup — 2026-10-30
@repo/uiInputforwards refs (RHF / autofocus).EmptyStatetitleElementallowsh1for page-level empty states (onboarding permission gate).
Admin shared table / filter / KPI format — 2026-10-30
- Align platform-admin list surfaces with console
DataTableToolbar+FilterBar/FilterSelectlanguage (indexes, tenants, keys, DLQ, jobs/billing/webhooks, connectors, audit, business reports). - Overview and business report KPIs use shared
KpiCard(optional hint) instead ofConsoleMetric; no second UI kit. - i18n: filter/search/empty-filtered strings for admin search-ops tabs in en/de/es/fr/ru.
Documents page UX (data-table reuse) — 2026-10-30
- Documents browse uses landed
@repo/uiDataTableToolbar/DataTableSelectionBar/DataTableSkeleton/DataTableColumnHeaderwith existingnuqsURL filters;DocumentsGridkept for spreadsheet editing. - Bulk export/delete moves onto
DataTableSelectionBar; loading usesDataTableSkeleton. - Row detail Sheet (
?doc=) shows metadata + redacted payload viaJsonPreview, with edit / history / delete actions (en/de/es/fr/ru).
Playground primary search layout — 2026-10-30
- Search Playground page leads with one Search workspace; Compare and Assistant open on demand via Tabs (
?tab=). Explain stays in a Sheet. - Compare runs two live queries through the existing oRPC search path (no parallel ranking). Drawer/stack Playground layout is unchanged.
Assistant UX (sources, cards, approvals, activity) — 2026-10-30
- Extend
@repo/aacsearch-uiwith Sources, AnswerCard, ApprovalCard, and ActivityStatus patterned on vendored assistant-ui (no CopilotKit / parallel chat kit). - Console assistant: domain-badge sources, richer answer cards, collapsible activity timeline, HITL interrupt approval wired to
assistantResumeInterrupt. - i18n: approval + activity strings in all five SaaS locales. Assistant remains behind existing settings/flags; no LLM in the search hot path.
Console Wave 1 UX (Schema / Mapping / Sync) — 2026-10-30
- Schema: compact field rows with shared FilterBar toolbar, capability grouping, and TanStack Virtual when lists are large (replaces card list + local pagination for the fields table).
- Mapping: Feed wizard rules and connector
MappingFieldsuse the same compact table + filter language as Schema. - Sync: runs table groups jobs by source; failed jobs surface first; job Sheet leads with error Alert + StatusBadge.
- Shared console helpers:
ConsoleListToolbar,VirtualRows,schema-field-groups,sync-job-groups(en/de/es/fr/ru).
Rules visual order / preview / conflicts — 2026-10-30
- Merchandising Rules tab: drag-and-drop evaluation order via
@dnd-kit(keyboard + pointer), still backed byreorderMerchandisingRulespriorities; Raise/Lower menu kept. - Conflict / precedence callout for overlapping match queries, competing pin/bury/exclude on the same document, and multiple redirects — HintTooltip explains existing evaluation order (no silent query rewrite).
- Merchandising Studio before/after preview shows applied-rule precedence (
reasons/ appliedRules) with an order hint. - i18n for new SaaS strings in en/de/es/fr/ru.
Shopping Guide need → field → value mapping — 2026-10-30
- Add shared searchable
Comboboxto@repo/ui(Popover + Command; no new UI kit). - Shopping Guide catalog binding UI maps need → field → value with column headers, Combobox field pickers, category value chips, and required-mapping progress — feature flag / existing guide settings unchanged; advanced JSON stays behind Collapsible.
- i18n for the new mapping chrome in en/de/es/fr/ru.
Onboarding Steps progress (Wave 3) — 2026-10-30
/onboardingshows unified@repo/uiSteps for the full setup journey (Create → Data → [Design] → Check → Connect); no Driver.js tour.- Create flow is two phases with one primary CTA each (path → Continue, then name/workspace → Create search); permission-denied uses
EmptyState. - i18n:
customerJourney.steps.create,continue,detailsTitle,pathHint,permissionTitlein en/de/es/fr/ru.
Data-table patterns on @repo/ui — 2026-10-30
- Add presentation building blocks in
@repo/ui:DataTableToolbar(composesFilterBar),DataTableColumnHeader,DataTableSelectionBar,DataTableSkeleton— no second UI kit; no TanStack/nuqs inside@repo/ui. - Wire org
IndexesTablewith TanStack Table +nuqsfaceted status filter, sort headers, row selection / bulk delete bar. AdminDataTableusesDataTableToolbar/FilterBarinstead of a bare search input (server list + existingnuqsquery state).
@repo/ui missing shadcn primitives — 2026-10-30
- Add shared primitives to
@repo/uionly: separator, slider, radio-group, breadcrumb, calendar (react-day-picker), pagination, toggle, toggle-group, hover-card, aspect-ratio, context-menu. - Export from
packages/ui/index.ts; keep unifiedradix-ui(no per-primitive@radix-ui/react-*deps). Deferred: sidebar, drawer/vaul (Sheet covers panels), carousel/embla, resizable, menubar, navigation-menu.
Console page density — 2026-10-30
- Paginate schema fields, facets, detected CMS sources/properties and sync history through the existing pagination UI. CMS properties and facets support field search; writes retain the complete underlying field collection.
- Remove nonfunctional source-selection controls from connection details; keep source selection in the connection wizard. Collapse inactive index navigation groups, and open the Playground assistant on demand.
- Align onboarding progress with the actual site/API steps, replace nested numbered design steps with section buttons, and remove position-dependent publishing instructions in all five locales.
- Validation: 372 SaaS tests, 17 i18n tests, SaaS TypeScript and architecture invariants passed; browser pagination/filter/navigation and 390 px layouts checked.
Commerce Flow Studio closure (visitor + versions) — 2026-10-30
- Visitor SSE:
flowTurnToStreamEventsemitsproduct_cards/ real ask option chips / honest groundedness; catalog+quote forwarded from the flow turn. - Advance claims session revision before shopping-guide quote/catalog side effects; quote create is idempotent per
(sessionId, sessionRevision). SearchGuideQuote@@unique([sessionId, sessionRevision])+ P2002 re-read closes residual concurrent quote create; migration20261030211000_search_guide_quote_session_revision_unique(deploy on authorized DB).- Assistant routing: trigger match before
defaultFlowId; cancel/clarify commands; techmap sessions can start from assistant; Shopping Guide bindings use schema field selects (JSON optional advanced only). - Studio draft try (
mode: "draft") works without enabling flows for visitors; public REST stays live-only. - Prisma
SearchFlowDefinition(org + index): fork builtin → draft → validate → publish immutable versions; runtime resolves builtin or published org flow. Migration20261030210500_search_flow_definition(deploy on authorized DB).
Commerce Flow org definitions (Studio) — 2026-10-30
- Prisma
SearchFlowDefinition(org + index scoped): draft/published versions with@@unique([organizationId, indexId, key, version]); migration20261030210500_search_flow_definition. - Domain: fork builtin →
org.{key}draft, save draft, validate, publish (immutable published row + next draft); runtime resolves builtin or published org flow by id/version (Studio draft try via P4draftflag). - Console oRPC:
listOrgFlows/getOrgFlow/forkFlow/saveFlowDraft/publishFlow/validateOrgFlow; Flows Studio fork/edit collect/validate/publish UI (en/de/es/fr/ru).
Index Studio navigation — 2026-10-30
- Existing index editors are grouped into Overview, Data, Studio, Validation and Results. Existing URLs, command navigation and capability checks are preserved; task-group breadcrumbs do not link to an editor with potentially different permissions.
- Group labels are available in English, German, Spanish, French and Russian. This introduces no project hierarchy or new persistence model.
Commerce Flow Agent wave 3 — 2026-10-30
- Techmap flow sessions seed nested guide state;
catalog_match/quotereuse shopping-guide product helpers (coerceGuideState, persist viasyncFlowFromGuide) and upgradependingActionstoresolved/incompletewith optionalendpoint,catalog,quote. - OpenAPI types flow session envelopes (
pendingActionsstatuses + SDK regen); REST/SSE wire tests; docsapi-reference/commerce-flows(en/ru/de).
Commerce Flow Agent Studio — 2026-10-30
- Console Flows Studio at
/flows(AI nav): enable flag, default flow, definition preview, step list, JSON+YAML export via sharedexportFlow*, try/advance session via oRPC, pendingActions in the try panel. - oRPC:
getFlowsSettings/saveFlowsSettings/listFlows/getFlow/ flow-session create/get/advance; settings persisted with index revision bump. - Assistant continues an existing flow session when
conversation_idmatches; pending catalog/quote actions surface as SSEguide_actions; embed keeps proof ondata-aac-proof-*. catalog_match/quotesteps return structuredpendingActions(shopping-guide handoff / in-process resolve when guide state is attached).
Commerce Flow Agent — 2026-10-30
- Studio-ready YAML/JSON commerce flows with Flow Runtime (ledger + step cursor) on top of Search Contracts.
- Shopping-guide techmaps compile to
commerce.techmap:*flows; guide REST stays compatible and attachesflowRuntimeon the session state. - API changes:
GET /indexes/{indexSlug}/flows,GET /indexes/{indexSlug}/flows/{flowId},POST /indexes/{indexSlug}/flows/sessions,GET /indexes/{indexSlug}/flows/sessions/{sessionId},POST /indexes/{indexSlug}/flows/sessions/{sessionId}/advance(requiressettings.flows.enabled). - API changes / Commerce Flow: OpenAPI now types flow list/get and session create/get/advance envelopes (
pendingActionstyped; SDKs regenerated). - Assistant stream may start a custom commerce flow turn when flows are enabled and a trigger matches (techmaps remain on
/guide/*).
Widget renderer consistency — 2026-10-30
-
Search cache separates consent states and disables personalization after consent withdrawal. React cards honor configured field roles, safe links, images and localized prices; search options preserve explicit zero/false values.
-
Shared assistant SSE and React adapters retain retrieval proof metadata from the stream contract.
-
Hosted pages retain saved widget type and the complete builder configuration through the CDN renderer, which shares search/SSE with
aacsearch-ui; React composition remains explicit. Shared URLs retain query, page and recommendation seed. -
Constructor previews render recommendations and autocomplete with the same type-specific renderer as published widgets, using server-approved draft settings.
-
Console AI/MCP confirmation operations have explicit access policies; assistant mutations require content-write and publication capabilities.
Bitrix public features — 2026-10-30
- Bitrix 0.7.9 connects compact assistants and recommendations per site, adds voice input and opt-in photo search through the existing API with CMS permission checks.
Bitrix smart filters — 2026-10-30
- Bitrix 0.7.8 follows native property settings/order, supports numeric bounds and independent multi-select facet counts, and preserves filter state in navigation.
- Large schema updates respect the API request limit. Serialized CMS cache values are excluded from product description text. Site-owned adapters can narrow and relabel native filter properties without bypassing the applied schema.
Public CMS assistant — 2026-10-30
-
Bitrix 0.7.7 labels native availability as orderability, preserving the distinction between backorders and physical stock.
-
Bitrix 0.7.6 adds a configurable published-assistant component, preserves public URL schemes/ports and refreshes service endpoints during Marketplace updates.
-
Assistant retrieval supports opt-in, metered query preparation for natural-language questions. Public product prices, currencies and stock are included in answer context.
-
Assistant source cards no longer treat orderability as proof of physical stock.
-
Hosted assistant pages honor the URL locale and preserve it in shared links and iframes.
-
The embedded assistant explains the HTTPS requirement before requesting the microphone, in all five supported UI languages.
Resilience / console exports — 2026-10-30
- O7: engine circuit breaker on the search read path (
searchCollection, multi-search). AfterSEARCH_ENGINE_CIRCUIT_THRESHOLD(default 5) consecutive transport failures (UPSTREAM_UNAVAILABLE/UPSTREAM_TIMEOUT) searches fail fast forSEARCH_ENGINE_CIRCUIT_COOLDOWN_MS(default 10 s) with the sameSearchErrorand aRetry-Afterheader, then one probe closes or re-opens the circuit. Engine 4xx answers do not count. State is per process;=0disables. - O5:
/metricsPrometheus now exposesaacsearch_usage_outbox_pending(JSON already hadoutbox_pending) andaacsearch_engine_circuit_open/_trips_total/_short_circuited_total; JSON gainsengine_circuit. - E7: DocumentsBrowser «Export selected» downloads the selected live documents as
JSONL (
exportSelectedDocuments, oRPC console, ≤100 ids, private fields stripped for plain members, unknown ids reported). - API changes: OpenAPI now documents
409 INDEX_READ_ONLYonupsertDocuments,bulkUpdateDocuments,deleteDocument,deleteDocuments,restoreDocumentandwebhookIngest(behaviour unchanged since I21); SDK docs regenerated. A search that hits an open circuit answers502 UPSTREAM_UNAVAILABLEwithRetry-After.
Sync UI — 2026-10-30
- I28: «Что изменилось». Каждый connector run (generic feeds, CMS) пишет в
job.stats.changesточные счётчикиadded/updated/removedи до 50 примеров на тип (id,title); Sync → Job detail показывает список. Тот же delta-хеш, что и у импорта, второго diff-пайплайна нет. Роли с private-полями индекса получают только счётчики (stripSyncChangeSamples); списокlistConnectorsотдаёт счётчики без примеров. - I7: Sync «Расписания» теперь единый список — расписания коннекторов и
реиндекса индекса (
getReindexSchedule, I22) по ближайшему запуску. Новых планировщиков нет. - API changes: none (oRPC console only; public REST unchanged).
Index lifecycle — 2026-10-30
- I22: scheduled reindex.
settings.reindexSchedule(cron + IANA timezone, at most once per hour) is edited on the Sync page (getReindexSchedule/saveReindexSchedule, oRPC console). The scheduler tick (andrunJobsOnce) enqueues due indexes through the existingrequestReindex→enqueueReindexIndexpath withtrigger: "schedule"and asystemaudit actor. Guards: plan featurereindex, indexactiveand not read-only, no parallel rebuild (a busy index skips the window), per-index advisory lock, missed windows run at most once, batch cap per tick. Last outcome (queued/skipped_*/failed) is shown in the console. - I21: read-only index.
settings.lifecycle.readOnlytoggled on the Sync page (getIndexReadOnly/setIndexReadOnlyMode, audited). While on, search and manual reindex keep working; document import/update/delete/restore/rollback, console patch, webhook ingest, connector runs (manual and scheduled — the scheduler skips the window without a failure), file/documents-import enqueue and dead-letter replay are refused. - API changes: document write endpoints (
POST/DELETE /indexes/:slug/documents…,restore, bulk update/delete by filter, signed webhook ingest) return409 INDEX_READ_ONLYwhile the index is read-only. No new public routes.
Documents / geo — 2026-10-30
- I30: console document history — per-field diff between revisions
(
getDocumentRevisionDiff, private fields stripped for plain members) and append-only rollback (rollbackDocumentRevisionwrites the old body as a new revision through the ingest path).listDocumentRevisionsrows gainhas_payload. oRPC console only; no public REST change. - Fix:
restoreDocumentwrote a second revision row after the import's own, so the next write to that document hit(documentId, revision)uniqueness. - K6: Playground geo bar (radius filter + nearest-first + per-hit distance).
- API changes:
sort_bynow accepts the native Typesense geo form<geopoint field>(lat,lng[, exclude_radius: 2km][, precision: 0.1km]); only a literalgeopoint(…)passed validation before, so every real geo sort was rejected withINVALID_QUERY. Plan gating (geoSearch+customSort) unchanged.
Tooling — 2026-10-30
- CLI: remaining CocoIndex-inspired gaps — tree-sitter WASM Rust/Java/JSON/Bash;
per-chunk
#cNcontentHash memo (skip identical bodies); Docling--no-ocr+parserOutputHashin state; live coalesce + one retry; richersync-status/doctors(parsers, grammars, reembed); connector-bridge NDJSON example; update summary includes chunks wrote/reused (--jsontoo). - CLI: Docling PDF (
--parser auto|docling|pdf-parse) withpdf-parsefallback; tree-sitter semantic--local-chunkfor code (WASM JS/TS/Python/Go); APIreembedafter upserts (reembed: auto);live/ event-drivenwatch+ optional--stdin-eventsfilesystem CDC bridge (DB/Kafka CDC stays connectors). - Customer CLI
integrations/aacsearch-cli(aacsearch): local file crawl + index/search/import/export/status via the generated@repo/sdk→ AACSearch API only (no local engine process). Incrementalupdate/watchwith content-hash + transform-fingerprint memoization, delete-sync, globs, declarativeaacsearch.yaml/.aacsearch/pipeline.json, optional API--chunkand local--local-chunk, PDF via optionalpdf-parse, URL-list source (--urls, public http(s) only), crash resume checkpoint,lineage/sync-status(doctors). Legacyscripts/aac.mjspoints here. - CLI gap-close:
suggest,multi-search,indexes,create-index,delete <id>,reembed; CSV rows→docs; HTML<title>+ strip on files/URLs; pipeline schema validation +examples/; global--json; Windows watch note / poll fallback. - CLI polish: root/integrations README + AGENTS mention;
examples/README.mdlocal:3000walkthrough; env-onlyAAC_API_KEYwithoutinit; clearer missing-scope / index-bound key Hints; docs en/ru/de scope note. - CLI CocoIndex-inspired upgrades (still API-only via
@repo/sdk):.gitignorehonor + exclude!negation; markdown frontmatter → title/tags; symlink / max-file-size guards; bounded--concurrencyupserts; update summaryinserted/updated/skipped/deleted;init --pipelinescaffold; sync logic state migration;--preview/--full-reprocess/-qaliases.
Search quality / ranking / billing — 2026-10-30
- F1: golden sets store
topK; CSV import/export; run compare + playground deeplink;assertQualityGateon default profile, enabled rules, synonym import; console quality-gate toggle (settings.quality.gate). - F2:
compareProfilesPreview/ oRPCcompareSearchProfiles(ephemeral configs only — no virtual replicas). - K8: widget
itemHtmlstored HTML DSL (sanitized placeholders); ecommerce wizard preset setscardLayout: grid, facets, currency/price roles. - K10: offline answer-eval harness (
gradeAnswerCase/evaluateAnswerSet). - R2: shared
decay.ts(linear/exp/gauss);withDecaySortinprofileToSearchParams; popularity/recs use sharedexp. - B6:
SearchUsageOutboxwritten on reserve, settled on commit/release; scheduler drains stale pending rows;/metricsexposesoutbox_pending. - B9: DSAR erase returns signed
ErasureReceipt(subject hash + HMAC); optional durable job viaDELETE …/subjects/:key?async=1(202{ job_id }). - I15/I20: reindex refuses alias swap when
settings.quality.gatefails. - F2: Profiles console compare UI for two saved recipes.
API changes — 2026-10-30
- Added
DELETE /indexes/{slug}/subjects/{subjectKey}?async=1(orPrefer: respond-async) →202 { job_id, status, receipt }; pollGET /jobs/{jobId}forresult.receipt/result.deleted. Sync erase (200) unchanged by default. - Added
POST /indexes/{slug}/documents?async=1(or Prefer / auto when batch ≥SEARCH_DOCUMENTS_ASYNC_THRESHOLD, default 100) →202 { job_id };?async=0forces sync. Job typedocuments_import. - Added
GET /jobs/{jobId}?stream=1(orAccept: text/event-stream) SSE progress frames until terminal status.
CMS modules — 2026-10-30
- Bitrix
aacsearch.search0.7.5: multi-site storefront ANDsbx_site_ids:=SITE_IDon search/suggest/recommend only when the install has more than one site (matches Exporter); new component templatessearch.titlecompact/dropdown,search.pagegrid/list, recommendations carousel; Marketplace updater copies intolocal/componentsandbitrix/components. - DIAFAN
aacsearch2.0.6: storefront bool filters, price range + sort, attrs AND-grouping, SSRF-safe API URL on save/connect, fallback heartbeat status, optional click tracking (/aacsearch/track/), filter-only search.
SaaS search UI — 2026-10-28
- Playground opens the shared InstantSearch widget by default; advanced search deep links and drawers retain their query/filter behavior. RAG uses the same Assistant UI workspace, conversation history and index settings as the assistant page.
- SaaS dev startup builds embed assets. Sandboxed previews use in-memory routing; preview sessions renew before expiry and failed previews offer a shared retry UI.
Security — 2026-10-28
-
Refresh vulnerable runtime and build dependencies; centralize Next.js, React and their type/peer versions in the workspace catalog. Remove the unused legacy
mdxCLI dependency. -
SSO fetch guard preserves token
Requestbodies/headers/cancellation and omits endpoint URLs and raw error text from security logs. -
Dodo webhook identity metadata is normalized once after signature verification; numeric and boolean values cannot become organization/user references.
-
Bitrix 0.7.0 update archives include the storefront endpoints deployed by their updater. Local CI supports macOS process groups and direct loopback probes.
-
Bitrix HTTP transport reads native nested response headers correctly, so the generated SDK decodes JSON responses instead of returning raw text.
-
Project, collection and shared-source forms generate request UUIDs over HTTP development URLs as well as secure browser contexts.
-
Better Auth
account.encryptOAuthTokensseals Google/GitHub/SSO tokens at rest (XChaCha20-Poly1305). Backfill:pnpm --filter @repo/auth oauth:encrypt-tokens(#137). -
SSO sign-in IdP fetches go through
safeFetch(@repo/utils); unverifieddomainVerifiedproviders are refused; migration resets legacy unverified flags (#204). -
External cron
runJobsOnce(rollup|all)runs the same daily billing / key reminder / snapshot prune work as the in-process scheduler (#178). -
Quality eval calls
searchForIndexwithlog: falseand default profile params so replays do not poisonSearchQueryLog(#177). -
Engine failures are logged with
SearchQueryLog.failed/error_codeand excluded from zero-result rates (#180). -
DocSearch crawler honors robots.txt / crawl-delay / noindex (#154) and seeds from recursive sitemapindex with
stop_urls/follow_links(#199). -
Query-log retention uses each plan's
logRetentionDays, capped by env (#175).
API changes — 2026-10-29
-
Analytics
GET /analytics/zero-result-recoverytotals now include session attribution:recoveredRevenueCents,unrecoveredRevenueCents,searchesPerSession,recoveredAovCents(fromSearchClickEventjoined onqueryLogId). Suggestfacet_chipsmay includeaxis: "price"when the query carries an NL price band. -
Suggest: optional
include_facet_chipson GET/indexes/{index}/suggestand POST/embed/suggest(default on for empty /*/ ≤3-charq). Response may includefacet_chips(axis/label/query) for size/color/brand/material refine chips. Console oRPC already returnedfacetChips. -
Search ranking / recommendations (domain): property-aware signals are schema-first — known commerce keys +
prop_*/attr_*/… prefixes + index fields withfacet: trueonly (no open snake*case scan). Bitrixtsvet/razmercount when facetable. Diversify/personalize/recs pass schema; blocked plumbing (bx*\*,kind,status, …) never becomes affinity. Size hard filters reject single-letter false positives; keys withforcedFilterBysuppress guidefacetSuggestions. OpenAPIHitReason.kindlistsproperty. -
CMS one-click Bitrix (console ↔ module, no API key paste): oRPC
createConnectSession/getConnectSessionnow includesettingsUrl(Bitrixsettings.php?mid=aacsearch.search&state=…for an already-installed module;nullon other platforms).installUrlremains the Marketplace partner installer. SaaS adds/connect-cms(org hub + per-org page) so the Bitrix module button can start the same session flow without pastingaacs_connect_…/ API keys. Moduleaacsearch.search0.7.4. -
Hosting P0 (server):
GET /api/v1/embed/widgets/{publicId}has its own per-address rate-limit bucket (embed-meta, 600/min) instead of sharing the 60/min embed budget with session/search; the meta response keepscache-control: public, max-age=60and stays non-enumerable (same 404 for unknown/paused/unhosted). A 429 carriesRetry-After,x-ratelimit-*and the standardRATE_LIMITEDbody, so the hosted page can serve a cached copy instead of 404ing. Other embed routes keep the 60/min tier. -
Hosted pages (D1):
POST /api/v1/embed/widget-sessionnow recognises a hosted page from the request itself —Originmust equal the origin the request was addressed to (scheme://Host;X-Forwarded-Host/Protoonly behind a trusted proxy) — in addition to the configuredNEXT_PUBLIC_SAAS_URL. Hosted pages serve correctly when reached via localhost/LAN/another host while the env names a different one; a foreign customer origin still answers 403ORIGIN_NOT_ALLOWED, and preview-token and allowlist semantics are unchanged. -
Saved widgets now store sparse overrides of the index embed settings instead of a full config snapshot: new oRPC
getSavedWidget(GET /search/indexes/{indexId}/widgets/{widgetId}) returns the storedconfig(overrides only) pluseffectiveConfig(defaults ← index embed ← overrides), andupdateSavedWidget(PATCH /search/indexes/{indexId}/widgets/{widgetId}) accepts the complete widget form and persists only keys that differ from the current index embed, so later index-levelPUT /embedchanges still reach published widgets.saveSavedWidgetpublishes sparse as well andduplicateSavedWidgetre-sparsifies the copied config; anullvalue is an explicit per-widget unset. Console policies cover both new routes. -
Widget publish history + rollback (G10): migration
20261029043000_search_widget_revisionsaddsSearchWidget.configRevisionand the append-onlySearchWidgetRevisiontable. Every widget mutation (publish/create, per-widget update, duplicate, restore) writes its snapshot in the same transaction as the widget row. The history is read withGET /search/indexes/{indexId}/widgets/{widgetId}/revisions(cursor pagination) and rolled back withPOST /search/indexes/{indexId}/widgets/{widgetId}/revisions/{revision}/restore, which replays a snapshot as a newrestorerevision (history is never rewritten). Responses are{items:[{revision,action,name,type,createdAt,actorUserId}],nextCursor}and{widget,revision}; both are org+index+widget scoped. -
Per-widget analytics (G7):
GET /search/indexes/{indexId}/widgets/{widgetId}/analytics(?days=1…90) aggregates the existingSearchQueryLog/SearchClickEventwidgetIdattribution into{days,searches,zeroResults,zeroResultRate,clicks,ctr,topQueries}(rates 0–1; top 10 reportable queries; failed searches excluded). Console policies cover the analytics/history routes. -
Server contract accepts the
datewidget facet type (WIDGET_FACET_TYPES); persisted widget configs may setfacetTypes[field] = "date"ahead of the builder picker / embed runtime slices. -
C22 database connector closed: oRPC
probeDatabaseConnector+ console «Test connection»; MySQL/Mongo probes verify table/collection; compose profiledatabase-connectors(mysql/mongo); CI jobdatabase-connectorruns live probe/page smoke for all three dialects. Console exposes TLS and advanced id/cursor/deleted columns; live suite covers keyset paging and soft-delete tombstones. -
F5 bulk synonyms: console Import/Export on the Synonyms tab; oRPC
importSynonymsBulk(JSON dump / array / CSV paste, replaces the set) andexportSynonymsDump; parser insynonym-bulk.ts(cross-index paste drops source set name so dumps move cleanly). Wizard no longer reads deprecated index environment. Import is all-or-nothing: invalid rows are refused with their CSV line / JSON index (SynonymBulkParseError) instead of being silently dropped, dumpversionis enforced,localesurvives export→import, and a rejected paste never partially replaces the live set.removeSynonymnow checks thesynonymsfeature like the other mutations. -
Console operation policies cover the C22/F5 procedures and the LTR routes (
GET …/ltr/models,POST …/ltr/train,POST …/ltr/models/{id}/activate); those console actions previously answered 403 “no project access policy”. -
Database connector: host-form drafts require an explicit database name (no silent
postgresdefault), the import confirmation is destructive, the 2000-group cap is stated in the hint, and thedatabase-connectorCI job runs in a container so its Postgres/MySQL/Mongo service hostnames resolve. -
Wizard & UX sprint close: per-query profile weights (
ProfileConfig.queryWeightsresolved inprofileToSearchParams); embed limits raised (facets 20, sort 10, perPage 48); low-CTR reports expose IPS-adjustedctrIps;WizardShellchrome forSearchWizard; Schema «Add all» discovered fields via field-classifier. -
Deep personalization without Typesense personalization-model archives (N21):
personalize=truere-rank uses query-history content affinity (works with zero click events), brand/category/tag facet affinity, popularity prior, and rank guards (navigational skip, relevance gate, max uplift +8, top-1 lock). Responsepersonalized.reason/movedexplain the reorder. Still opt-in viasettings.recommendations+ consent; no engine personalization models. -
AI agent marketplace (N19): MCP tool review gate (
mcp-tool-review.ts+ catalog test) — every generated operation is allowlisted or explicitly denied; unreviewed ops fail CI. Console Settings → AI agents lists reviewed tool categories and Claude / Cursor / generic MCP OAuth connect snippets. Builds on MCP OAuth (#20); credential minting and privacy exports stay denied. -
HyDE for hybrid semantic search:
settings.search.hyde = { enabled, url, model?, timeoutMs ≤ 150 }calls an operator HTTP endpoint for an embedding used only invector_query(Typesense floats instead of auto-embed[]). Lexicalqis never rewritten; no in-process LLM. Response fieldhyde: "applied" | "skipped:<reason>";explain.hydewhenexplain=true. Metricsaacsearch_hyde_*. Console Relevance → HyDE section. Off by default. Cross-encoder rerank (settings.search.rerank.provider = "http") unchanged (M13-1). -
Custom ML ranking (LTR, #152):
settings.search.rerank.provider = "ltr"runs a local pairwise L2-logistic model on first-page hits (IPS-weighted click pairs, no LLM). Async jobltr_train; models undersettings.search.ltr; oRPClistLtrModels/trainLtrModel/activateLtrModel. Console Relevance → Rerank provider switch. Off by default; activate a candidate explicitly. -
Feed preview identity + schema mapping (I12 / I13):
POST …/feed-preview(and oRPCpreviewConnectorFeed) returnsidentity(natural/hashed/mode/autoId). Auto-id hashes sku → url → title (never body). Suggested mapping falls back tosku|urlwithfirst_non_empty. Console wizard shows mapped-document preview and an auto-id warning; multi-source transforms edit asa | b. -
Database ingest connector (
type: database, dialectspostgres/mysql/mongodb): table/view → documents with composite(cursorColumn, id)keyset paging, sealedconnectionUri/password, SSRF host guard, read-only sessions, soft-delete viadeletedColumn, and immediate follow-up runs whenhasMore(#155 / C22). -
Hybrid explainability: each
SearchHitmay includeincludeExplain(textfrom Typesensetext_match_info,vector.distance,hybrid.rankFusionScore);hit_reasonsaddshybrid/vectorkinds and richertextdetail. Mapped in@repo/search-engine(includeExplainFromNative). -
Prompt registry + model router (K9):
@repo/aigains versionedrenderPrompt/rollbackPrompt(AI_PROMPT_PIN_<id>) andresolveTextModel({ operation, requested?, planId? })with per-operation output budgets; answer / synonyms / shopping-guide / stream use them.getAssistantSettingsalso returnsmodelCatalog+promptCatalog. -
Merchandising rule engine (F7): console gains banner / query-rule match (
queryExact,whenZeroResults) / campaign schedule (daysOfWeek,hours) editors, priority raise+lower, dry-runPOST …/rules/preview, and Algolia-style before/after studio via oRPCPOST …/rules/studio(compareMerchandisingPreviewskipMerchandisingsearch for engine-order hits; missing-externalIddiagnostics + light Relevance Inspector reasons on the after column). Domain redirect / banner / pin / bury / boost / exclude / diversify + priorities were already live.
-
Zero-result recovery report (O1):
GET /analytics/zero-result-recoveryreturns top asked-zero queries with dominantfallbackStep, cause bucket (unrecovered/typo/keyboard/broadened/other), recovery rate and window totals; consoletenantAnalytics.zeroResultRecoveryplus synonym draft deep-link (rules?tab=synonyms&suggest=). Built from existingSearchQueryLog.zeroResults+fallbackStep. -
Query reports (P1-15 / F14):
GET /analytics/query-reportsreturns low-CTR, declining-volume and revenue-per-query lists (days,limit, optionalindex); consoletenantAnalyticsincludes the samequeryReportsblock. Built fromSearchQueryLog+SearchClickEvent(valueCents/queryLogId); PII / hashed digests are filtered out of the rankings. -
Popular searches per category / locale / window:
SearchQueryLogstoreslocale+category(migration20261029010000_…);GET /analytics/popularand filteredGET /analytics/trending; suggest/presearch acceptcategory/locale/days. Search may send analyticslocale/category(else script detection + singlecategory:=from filters). -
Presearch personal recent chips read
SearchRecentQueryby durable visitor id (client_id/session_id) before falling back to the query log, so hashed logs underSEARCH_HASH_QUERIESno longer blank the empty-box history.POST /embed/presearchandGET /indexes/{index}/presearchacceptclient_idas an alias ofsession_id; embed search andPOST /recentshareupsertRecentQuery(PII / digest strings are not stored). -
Search
understandingon REST/indexes/{index}/search, embed search and the console Playground now reports rating / year / warehouse / lead-time NL filters (rating_filter,year_filter,warehouse_filter,lead_time_filterplus matching*_filter_byfragments). Caller’sqis unchanged for these phrases; fragments AND intofilter_byonly when the schema has a matching field. Private-field scrubbing covers the new fragments. -
Index search defaults add
attributeUnderstanding(default on): gates those NL attribute filters from the Relevance console. Schema hints exposeratingFields/yearFields/warehouseFields/leadTimeFields. -
Built-in Relevance templates: eight advisor presets (
ecommerce,marketplace,b2bCatalog,classifieds,knowledgeBase,docsSite,supportFaq,media) and eight search-profile recipes (profile-templates.ts) for the Profiles create dialog. -
Index search defaults and profiles accept Typesense
query_by_weights(one integer perquery_byfield). Relevance Essentials and the Profiles create dialog edit weights per field; misaligned values are rejected on save and dropped on read. -
Numeric facet
histogrambuckets (equal-width bands fromstats+ discrete value counts) on search facets andPOST/GET …/facets/:attribute/search. Console Facets preview and Playground render the distribution; not Elasticsearch-style aggregations. -
Federated autocomplete by type:
POST /embed/suggestnow returnskindswhensettings.suggest.kindFieldis set; the embed dropdown and console Suggestions panel render one section per kind (flat list unchanged when unset).
Hosting, widget constructor and console UX — 2026-10-29
- Hosted customer surfaces (
/w/{publicId}) are usable across deployment hosts: the widget-session hosted-page bypass now accepts the origin the request was actually addressed to (requestHost/forwarded host behind a trusted proxy) as well as the configured deployment origin — a page served fromlocalhostwhileNEXT_PUBLIC_SAAS_URLnames another host no longer 403s withORIGIN_NOT_ALLOWED. Foreign origins and missingOriginstill fail closed; preview tokens and the origin allowlist are unchanged. - The public hosted-widget meta route has its own rate-limit tier
(
embed-meta, 600/min/IP) instead of sharing the 60/min embed bucket, so server-rendered hosted pages do not exhaust a single platform-wide IP bucket; 429 responses keepRetry-After/x-ratelimit-*. - Hosted deep links:
?q=,?page=,?locale=,?doc=(recommendations seed) and?surface=searchare applied by the loader (explicitaac_*URL state still wins), plus brandedloading/errorstates, a clear “link expired” page for stale previews, shared hosted-link/iframe hints and theme chrome passthrough (radius,fontFamily,density,showPoweredBy). - Per-widget configuration: publishing stores sparse overrides against the
current index design instead of a full snapshot, new
getSavedWidget(GET) andupdateSavedWidget(PATCH) procedures expose one widget's stored overrides + effective config, and the console has a per-widget editor modal. Re-publishing a widget (hosting/origins) no longer overwrites its customisation, and index-design changes still reach keys the widget does not override. - Builder parity with the server contract: facets 20, sort options 10,
highlight fields 8,
hitsPerPageOptionsup to 8 values 4–48,perPage4–48, and string sorts (name:asc,_text_match:desc) — both the builder and the widget runtime. - Assistant/voice: assistant-ui message queue in console and hosted chat; microphone capture with a level meter (localised permission/unsupported states) in the composer and Playground voice mode; the embedded assistant gained dictation, read-aloud, retry and a mic-unsupported message; hosted assistant labels follow the widget's string overrides and its speech language follows the widget locale; InstantSearch voice search templates are localised.
- Adaptive console: documents become one card per row on phones with a
sticky Actions column on desktop; admin tables use mobile cards and pin
identity/action cells; the admin Search-ops tab strip collapses to a single
section select (Tenants and Engine included); shared primitives (checkbox,
switch, select, menu items, dialog close, help buttons) get ≥40 px coarse
pointer hit areas; the consent banner is compact on phones; dashboard and
members tables stack.
ConsoleTableFrameno longer forces a 448 px inner width on a 390 px viewport, so sticky action cells stay visible. - i18n: the synonym-import placeholder is no longer raw JSON inside an ICU
message (rules page
MALFORMED_ARGUMENTfixed); the relevance weights tip points at the existingprofiles.weightsTipkey; new hosted/builder/ assistant/voice keys land in all five locales (parity 5 461 keys).
API changes — 2026-10-28
- Search Projects and index environments (
production/staging/development) removed from the product. Console project/catalog oRPC routes are unmounted;POST /indexesand clone always createproduction; API keys are never environment-bound; slug uniqueness is per organization (migration20261028220000_drop_index_environments). GET /rules/{ruleId}/revisionslists newest-first merchandising rule snapshots (settings:write, same as other/rulesroutes); console oRPClistMerchandisingRuleRevisionsmirrors it.- Organization GDPR purge (
DELETE /organization/data?documents=true) fails closed withENGINE_PURGE_FAILEDwhen Typesense document delete errors (missing collections still skip). - Diversify/MMR curations are written only on settings/ranking paths; search
prepareDiversifyis read-only and rejects metric mismatch. - Client IP for rate limits / forms respects
TRUSTED_PROXY_MODE/TRUSTED_PROXY_HOPS(default proxy hops) so spoofedX-Forwarded-Forleft hops are ignored. - REST search preserves the API key's forced filter when applying a saved search profile. Profile defaults cannot replace key restrictions; request and response schemas are unchanged.
SaaS search integration — 2026-10-28
API changes
-
Public schema descriptions use logical index terminology;
legacy_highlightsmaps to the internal highlight compatibility flag without exposing engine versions. -
Search feature entitlements apply to unbilled console previews as well as API traffic. Native geo filters/sorts and auto-embedding
query_byare gated before execution. -
Direct scoped engine keys default to
403 DIRECT_SEARCH_DISABLED. Managed SaaS browser clients use embed sessions; self-hosted operators can explicitly enable compatibility withSEARCH_ALLOW_DIRECT_ENGINE_KEYS=true. Run the ACL upgrade script to revoke previously issued direct parents on SaaS rollout. -
Managed references default to
asyncReference=true,cascadeDelete=false; synchronous references and engine cascades are rejected to preserve PostgreSQL mirror/accounting. Native capabilities remain visible to platform administrators. -
Document usage and per-index limits exclude tombstones; restoring a deleted document reserves quota again.
-
Partial
update/emplaceimports preserve omitted fields in the PostgreSQL rebuild payload. -
SDK generators cover inline request bodies and enum/union/array types in TypeScript, optional server-side platform admin credentials in TS/PHP/Python/Go, and no automatic replay of non-idempotent TS operations.
Search administration and managed schemas — 2026-10-28
- Added the platform-only search engine console: collection ownership, cluster resources, diagnostics, runtime configuration, startup validation and queued maintenance. Writes use the existing domain services and audit; platform credentials remain server-side.
- Customer schema editors preserve advanced fields and collection settings, resolve logical index references and dictionary IDs, and plan rebuilds for changes to existing engine options. Additional safe search defaults can be edited and persisted without allowing tenant/filter overrides.
- Customer index/schema/ranking responses omit physical collection names; sync jobs expose
canRollback. Admin index deletion uses the shared locked lifecycle and rebuild guard. - Public product copy describes AACSearch services instead of its internal search provider in all five locales. Documentation in English, Russian and German uses the current public health/voice responses, logical index names and the managed API; competitor comparisons retain their own product names.
API changes — v1
PATCH /indexes/{indexSlug}/schemaaccepts the shared field contract and collection settings, including settings-only patches. Advanced properties survive merges; nested paths are validated. Create accepts up to 250 declared fields; each patch accepts up to 100.- Expanded schema field response metadata to include reference, tokenizer, stemming, storage, range and vector/HNSW settings. The SDK and OpenAPI are regenerated from operation metadata.
- Schema embeddings support source mapping, query/document prefixes, a provider path, recommendation models, and Vertex AI project/region/tasks with OAuth or service-account authentication. Secrets are encrypted and masked; changing credential destinations requires re-entry. Custom token endpoints receive the same URL/DNS validation as embedding endpoints.
API: Typesense 30.2 integration and protocol audit — 2026-10-28
- Add platform-admin engine catalog/readiness, diagnostics, native resource management,
runtime/startup validation and leased maintenance jobs. Stored credentials are
redacted; writes require
settings:write, use the existing admin gate/rate limit, and invalidate affected search caches. Managed aliases remain behind publication guards. - Expand shared REST/oRPC search options, per-field typo/prefix/infix and multi-field
grouping; forward union
remove_duplicates=false. Correctfacet_sample_slopeto uint32; fractional inputs now fail validation instead of failing upstream. - Extend collection field contracts with string*, tokenizer and asynchronous JOIN
options, voice configuration and numeric-array range indexes. Reject unsupported
l2distance instead of sending an invalid Typesense schema. - Fix the live-proven ACL empty-array filter bug with server-derived
_acl_public, guarded legacy backfill, cache versioning and retirement of v3 scoped parents. - Route native NL/voice document retrieval through the shared search pipeline; force curated hits to satisfy tenant/ACL filters. Audio stays in the request body.
- Replace the unsupported Compose memory flag with max-indexing-concurrency and explicit analytics settings. Running deployments are not restarted by this change.
- Add administrator-only native recommendations through the shared search pipeline, and translate personalization model metadata/archive uploads to the native wire format.
- Record all 101 native routes, 57 startup settings and 88 parser options with
explicit integration boundaries in
docs/reference/typesense.md.
Security: API key hard restrictions (first slice) — 2026-10-28
SearchApiKeygainsforcedFilterBy,allowedFields,allowedIps,rateLimitPerMinute,profileSlug(migration20261028083200_search_api_key_restrictions). Existing keys stay unrestricted.- Auth middleware enforces
allowedIps(403 IP_NOT_ALLOWED). Per-key RPM overrides the global rate limit (capped bySEARCH_RATE_MAX_REQUESTS). - REST search AND-merges
forcedFilterByand bindsprofileSlug(experiments cannot swap a bound profile). Rotation copies the new fields (#139).
1C-Bitrix module aacsearch.search 0.7.1 — 2026-10-29
CMS connector checklist MUST gaps (#52 / #53 / #168 / #169):
- Catalog events
OnPriceAdd/OnPriceUpdate/OnProductUpdatequeue price-stock upserts;OnSuccessCatalogImport1Crequests a reconcile pass after mass 1C exchange that may skip per-element handlers. - Sale
OnSaleOrderSaved/OnSaleOrderPaidnudge conversion drain on the next tick (no SaaS call in the order hit). Agent ticks collect orders whentrack_behaviour=Yeven if interactive consent gates views/carts — saltedFUSERonly, no PDn (Marketplace 2.E hashed identification). - Pack:
install/updater0.7.1.php+scripts/pack-marketplace-update.sh 0.7.1.
1C-Bitrix module aacsearch.search 0.7.0 — 2026-10-28
Ported from the older module generations (1.8.6 aacworkflow.aacsearch, AACAPP connector, shogo, aac.search 1.2.0) after a side-by-side comparison of all five (#132).
- SEO text in search. The META_TITLE, PAGE_TITLE and META_DESCRIPTION the shop set on the element itself (
b_iblock_ipropertyENTITY_TYPEE, value fromb_iblock_element_iprop) are appended tocontent. Iblock and section templates are skipped on purpose: on Termokit they are one sentence around the name for every product («Купить {=this.Name} с доставкой…»), so they would make «доставка» match the whole catalog. A template without a stored value is computed byInheritedProperty\ElementValues, at most 200 per batch. - Price type (option
price_group_id, «Тип цены в поиске», default: the base type as before).pricecomes from that type, andGetOptimalPricealways gets that one row, the base one by default. Before, it priced every type the guest may buy by, and on Termokit that includes «Закупочные»: a single discount, or modeY, could have indexed the purchase price. - Wholesale tiers are ignored: only
b_catalog_pricerows that hold quantity 1 make the price (QUANTITY_FROMempty or ≤ 1,QUANTITY_TOempty or ≥ 1), the same rule Bitrix uses. - Prices stay fresh:
- A catalog discount added, changed or deleted (
OnDiscountAdd/Update/Delete) starts one full sync, after 120 s without further changes and at most once an hour. - An action whose
ACTIVE_FROM/ACTIVE_TOpasses does the same: the tick pollsb_catalog_discount, andb_sale_discount(cart rules) too in modeYor on shops withuse_sale_discount_only(Termokit). - This happens only while discounted prices are, or just were, in the index. During that full run delta waits, so on a 300k catalog a promotion boundary costs one full pass.
- A deleted price row (
Bitrix\Catalog\Model\Price::OnBeforeDelete) and a deleted SKU offer (OnBeforeIBlockElementDelete→CCatalogSku::GetProductInfo) re-export their product through the queue.
- A catalog discount added, changed or deleted (
- Private properties stay out: purchase price, cost price, margin or markup (
PURCHASE_PRICE,COST_PRICE,PRIME_COST,SEBESTOIM*,ZAKUPOCHN*,NACENK*,MARGIN,MARKUP, «закупочн», «себестоим», «наценк», «марж»).- They are never promoted and never go to
attrsorcontent. - The rule also holds at export time, for mappings stored by 0.6.x and for fields an admin enabled.
DELIVERY_COST,MIN_PURCHASEand «Минимальная закупка» stay shop data.
- They are never promoted and never go to
- No raw ids in facets: a list value whose enum row is gone, or a directory value missing from a directory that was read, is empty instead of its ID / XML_ID.
- Text normalization in
content,attrsand property text: NFC (with intl), zero-width characters, soft hyphens and control characters removed. Content hashes change, so the first sync after the update re-sends documents. - Measure ratio: new built-in field
bx_ratio(float, only when it is not 1 and all offers agree), and the guide cart rounds the quantity up to a multiple of the ratio. - Queue
b_aacsearch_queue:- New columns
REVISION,PRIORITYandNEXT_RETRY_AT, added byinstall/updater0.7.0.phpor lazily by the tick (optionqueue_schema). done()deletes only the revision it read, so a change queued during a push is no longer lost.- Price and stock changes are drained first.
- A failed row waits 1, 2, 4 … up to 120 min instead of retrying on every tick (still 20 attempts).
- New columns
- SelfTest: a new «Agents on cron» row warns when the catalog has more than 20 000 elements and ticks do not run from cron.
- It is advisory and does not fail the self-test.
- Ticks from cron agents and from the module's own
tools/sync.php --tickboth count.
- Queue index
ix_aacsearch_queue_pick (ACTION, PRIORITY DESC, ID)for the price- and stock-first read. - Schema upgrade survives two requests that upgrade at the same moment.
1C-Bitrix module aacsearch.search 0.6.1 — 2026-10-28
- The indexed price is the price a guest pays, with discounts (#129): each product and offer is priced
with
CCatalogProduct::GetOptimalPrice(all-users group, quantity 1, the iblock's site, no session coupons), the cheapest final price isprice, and the base price goes to the new built-in fieldold_price. Filters and price sorting now match the storefront. New optiondiscount_prices(«Цена со скидкой в поиске»):auto(default) prices with discounts only when an active catalog discount exists,Yalso honours cart rules,Nkeeps base prices. Cart rules are opt-in because on the Termokit stand 97 active rules discounted none of 3000 products while each call costs ~1.5–2 ms. The approach comes fromDiscountReaderof the 1.8 module, with detection on D7 tables. - The install wizard checks the database (#125): the module is MySQL / MariaDB only, so on the
PostgreSQL edition the requirements step shows a failed «MySQL / MariaDB» row and a CLI install stops
with a clear message before touching the site, instead of failing halfway through
install.sql.
Security: personalised CMS module ZIP needs the console ticket — 2026-10-28
- The personalised module download (
connect=1) no longer issues or rotates a CMS key on a plain GET and no longer takes the API origin from the URL. A cross-site link could send an admin's session cookie (SameSite=Laxallows top-level GETs) and produce a module pointing at a foreign API. The key and the origin now travel only inside the sealed ticket the console mints (#124). WithoutSEARCH_SECRETS_KEYthe console still shows the connection string, and the server logs a warning that the personalised ZIP is disabled.
API changes — v1
- Changed
GET /integrations/{platform}/module.zip?connect=1— requiresticket(from the console's connect-download call) unlessmode=code; without it answers 403CONNECT_TICKET_REQUIREDand mints nothing.api_urlis honoured only withmode=code; the key mode takes the origin from the ticket. The download no longer answers 402 (the key is issued by the console call).
Embed widget: assistant panel, InstantSearch runtime and card config — 2026-10-28
- The search widget runtime is built on instantsearch.js (
packages/embed-widget/src/{widget,search-client,routing,facet-plan,facet-widgets,results,refinements,drawer,insights,templates,strings}.ts): URL routing is namespacedaac_*so it never collides with the host page's own parameters; facet filters (list,range,toggle,hierarchy,select,rating,buckets) apply the samefilter_bythe console preview shows; a ruleredirectis followed only on an explicit submit, not while typing. AACSearch.init()acceptstemplates.item(a custom result card receiving the hit,escapeHtml, the cardrolesanddefaultCard) andcssClasses(extra class names appended to the card's elements).- New in-widget assistant (
packages/embed-widget/src/assistant.ts): when the index has the assistant enabled, a launcher button opens a popover that streams answers fromPOST /api/v1/embed/assistant/stream, with welcome text, suggested prompts, a stop button,[n]citation links to source cards (the result-card markup), thumbs feedback throughPOST /api/v1/embed/assistant/feedback, and the index's experience settings (showSources,showProductCards,allowFeedback,showAnswerMeta). The guide's "Ask the assistant" chips andassistant_promptblocks open the panel with the question;assistantUrlstays the fallback link when the assistant is disabled.AACSearch.openAssistant(question?)opens it from the host page. The widget keeps a local copy of the stream event types, type-checked againstassistant-stream-contract.ts; nothing of@repo/apiis bundled. New widget strings in all five locales:assistantTitle,assistantPlaceholder,assistantStop,assistantSources,assistantError,assistantSend,assistantHelpful,assistantNotHelpful.
API changes — v1
- Changed
GET /embed/widgets/{publicId}— newframeAncestors: string[](the widget's allowed origins; empty = any site). The hosted page/w/{publicId}sets itsContent-Security-Policy: frame-ancestorsfrom it, so the?embed=1variant can be framed only by the customer's sites. - Changed widget config (
widget.config/settingsonPOST /embed/widget-session,POST /embed/config, console widget builder) — new optional keyscardLayout(list|grid, defaultlist; result cards only —layoutremains the recommendations strip),facetBuckets(field →[{label, start?, end?}]bands of abucketsfacet),categoryField,badgeField(card roles, unset = conventional names / hidden);strings.<locale>overrides now also validatequeries,products,askAssistant,categories,filters,seeResults,clearAll,showMore,hitsPerPage,currentRefinementsand theassistant*keys above.
MCP OAuth and HTTP transport — 2026-10-28
- Complete the browser login, organization selection and explicit consent flow,
discovery endpoints and stateless Streamable HTTP at
/api/mcp. - Require signed continuation parameters, PKCE S256, exact resource/audience, live organization/project permissions and consent. Deny revoked grants and unavailable Redis storage; restrict anonymous registration independently.
- Align the OAuth schema with Better Auth 1.7.7 resource policies and token
rotation fields. Apply migrations before enabling
MCP_OAUTH_ENABLED. - Add isolated PostgreSQL/Redis OAuth integration checks and browser coverage.
Deployment and client registration are documented in
docs/MCP-OAUTH.md. - Limit CI Next.js prerender pools to two workers and build applications sequentially to avoid multiplying workers across the shared runner host.
Authentication — 2026-10-27
- Upgrade Better Auth, OAuth provider, SSO and passkey together to 1.7.7. Keep the verification and account lockout migration from the preceding 1.6 update.
- Deploy all authentication replicas together; users with an in-flight OAuth or SAML login must restart that login after the coordinated security upgrade.
Dependencies / security — 2026-10-27
- better-auth 1.6.5 → 1.6.33 and
@better-auth/sso,@better-auth/oauth-provider,@better-auth/passkey1.6.5 → 1.6.33 (last 1.6.x; closes the Dependabot critical/high alerts fixed in 1.6.11, 1.6.13 and 1.6.22, #111).better-call1.3.5 → 1.4.0 follows the core.packages/authnow declares@better-auth/utils0.4.2 so the plugins resolve the peer version better-auth itself pins (better-call 1.4.0 pulls its own 0.5.x). - Add the two-factor verification and account lockout columns required by the plugin.
Existing factors remain verified; new enrollment explicitly requires TOTP verification.
Apply
20261027222000_two_factor_verification_statebefore deploying the updated auth code. CI exercises enrollment, TOTP verification, account lockout and recovery against PostgreSQL.
Dependencies: hono 4.13 and axios 1.20+ (security) — 2026-10-27
honocatalog^4.12.14→^4.13.14(lock 4.12.12/4.12.14 → 4.13.14): closes the open Dependabot alerts (serve-static path double-decode GHSA-5r4p-p66f-jhc7, query parser after fragment GHSA-crvj-82cr-hjcx,parseBodydot-notation DoS GHSA-g6gw-c38x-mqfc,toSSGpath traversal GHSA-gqvv-2mrq-wpjv,hono/jsxXSS GHSA-hxh3-vqpv-xpqv). No breaking change forhono/cors(we setallowMethodsexplicitly, so the new defaultQUERYmethod does not apply),hono/logger,hono/route,hono/vercelorapp.use;app.mountandhono/cacheare not used. OpenAPI/SDKs unchanged (sdk:checkin sync).axios(transitive only:typesense@3.2.0andwait-onviastart-server-and-test) 1.15.0 → 1.21.0.start-server-and-testcatalog^3.0.2→^3.0.13(bringswait-on9.0.5 → 9.5.1, which already requires axios^1.20.0).typesense3.2.0 is the latest release and declaresaxios ^1.15.0. The rootpnpm.overridesentryaxios: "^1.21.0"keeps all copies on the patched 1.x line without admitting a future incompatible major release; a second overridehono: "catalog:"lifts the extrahono@4.12.xcopy pulled by@prisma/dev(^4.12.8) onto the catalog version so a single hono build is installed. Remove the overrides once those owners move their ranges.
Project shopping guides — 2026-10-27
- Added versioned expert tech maps, provenance-bearing session facts, deterministic quantity and package calculations, confirmed catalog bindings, exact-SKU recommendations, expiring quotes and idempotent CMS cart hand-off.
- The Bitrix module can opt in to exact active-offer documents for guides while its regular storefront search excludes those guide-only projections.
API changes — v1
- Added
POST /indexes/{indexSlug}/guide/sessionsand the session answer, recommendation, saved-project, quote and cart routes under/guide/sessions/{sessionId}. They requiresearch:read, use optimistic revisions and never let model output supply Typesense filters or cart prices.
Queued schema rebuilds — 2026-10-27
-
Breaking schema changes from the console and REST store an immutable target separately from the live schema and queue the rebuild. History remains planned until publication; cancellation and failures retain the working schema.
-
Publication rechecks the current configuration, schema version, membership, project publication grant or API-key validity and scope. A revoked grant/key or an intervening configuration edit prevents switching the alias.
-
Copy-phase failures finalize the job and migration in one transaction. Other migration failure writes check the current job lease. Lease recovery finalizes exhausted migrations before publication; an obsolete worker cannot fail a migration owned by its successor.
-
Bitrix, PrestaShop and DIAFAN keep pending targets separate from accepted mappings and confirm them only after a successful job poll. Rejected schema publication also leaves alias-scoped stemming dictionaries unchanged.
API changes — v1
PATCH /indexes/{indexSlug}/schemawithallow_reindex: truereturns 202 withapplied: false,migration_idandjob_id.applied: trueis reserved for an already applied schema. Poll the job for completion; enqueueing alone does not change the live schema.
Project roles and console isolation — 2026-10-27
- Organization owners and administrators can assign project administrator, developer, content manager, analyst and viewer roles. Publication and deletion require separate grants. Explicit activation preserves legacy read access until the organization enables project access control.
- Every mounted console search operation has a checked policy. Index lists, server-rendered pages, environment selection, analytics and secondary resource lookups respect project visibility; background workers do not inherit the enqueuer's console identity. Membership deletion removes its project grants.
- Project roles never grant organization administration, billing, API-key or private-field access. Existing private data cannot be exposed through schema changes, cached source previews, recommendation caches or normalized sidecars.
- Project team edits use revision checks and an audit in the same transaction.
Apply the
20261027104500_project_accessmigration before deploying this code.
Guarded snapshot publication — 2026-10-27
- Restores verify every configuration artifact against the manifest, as well as compressed document bytes and record counts, before publication. Damaged snapshots leave the live alias in place and suggest choosing another copy.
- A PostgreSQL row lock protects the final configuration comparison, alias swap and schema write. A concurrent schema, provider-key or settings edit prevents a stale restore from publishing; encryption-key rotation alone is compatible.
- Restore retains private-field restrictions from both the snapshot and current settings. Alias-scoped stemming is replayed after guarded publication so a rejected restore cannot change the live dictionary.
Embedding credentials at rest and snapshot restore — 2026-10-27
- Provider keys and credential URL parameters are sealed before schema writes; runtime collection creation and rebuilds decrypt them. Key rotation backfills legacy fields and migration plans with conditional writes that preserve edits.
- New snapshots mask embedding credentials. Restores resolve those references against the current connection and refuse unmatched credentials before the alias swap; historical keys are never reused from a snapshot.
- Embedding model, source field, endpoint and key changes produce a rebuild plan. Creating an auto-embedding field enforces the hybrid-search entitlement.
Embedding credentials in schema responses — 2026-10-27
- Console index lists, ranking, engine fields, schema history and migration results mask provider keys and URL credentials. REST schema plans mask the same values, including dry runs and rebuild-required errors.
- Schema edits preserve masked or omitted keys at the original provider and endpoint. Changing either requires explicitly supplied credentials; new connections reject masks and ciphertext. Unrelated field edits keep the working vector provider configuration and original signed URL bytes.
- Partial schema updates keep unsubmitted field definitions; removals remain explicit, so another field edit cannot discard a working provider connection.
Embedding provider credential routing — 2026-10-27
- Custom embedding URLs never inherit the platform OpenAI credential. They receive only an explicitly supplied customer key. Built-in models and other providers do not receive the OpenAI server key; default OpenAI models retain managed authentication. This applies to creation, field additions and rebuilds through the shared engine schema builder.
Conflicting records within a source — 2026-10-27
- New isolated feed and native CMS connectors coalesce identical repeated IDs and quarantine IDs with differing mapped content. Other valid records can proceed; accepted versions, cursors and full-inventory deletion stay protected.
- XML and YAML carry conflict diagnostics before compatibility deduplication. Connection checks reject ambiguous ID mappings before applying a candidate.
DUPLICATE_SOURCE_IDprovides a recovery hint in every console locale. Quarantine stores mapped positions and a source pointer, without conflicting content. Retry reads the corrected current source. Existing legacy connectors retain their previous last-record policy.
Source configuration changes during synchronization — 2026-10-27
- Effective connection, mapping, file-manifest and schedule edits fence running
jobs with
configRevision. Superseded jobs reportSOURCE_CONFIG_CHANGEDwithout overwriting current cursors, freshness, failure counts or schedules. - Data-setting edits clear the previous cursor and require a verified fresh synchronization. Draft edits, connection checks without a candidate and credential re-keying preserve active work.
- File-manifest changes queue one fresh follow-up instead of attaching the UI to an obsolete running job. Skip-set writes are conditional on the revision.
- Deployment requires
pnpm --filter @repo/database migrate:deploy.
Isolated connector identities and CMS inventory deletion — 2026-10-27
- New pull and file connectors isolate record IDs within their connector. The migration preserves existing connector IDs and native record IDs; accepted records retain their native identity for provenance.
- Source writes check ownership under the same index lock as engine/mirror
import. Conflicting legacy sources fail with
SOURCE_ID_CONFLICTand a recovery action in all five locales. Cancellation is checked after lock waits. - A complete CMS inventory reconciles missing records even when its provider cannot report deleted IDs. Partial reads never infer deletion; empty inventories require a full run. Deletion and pending cleanup stay scoped to the source.
- Two-site WordPress engine checks cover equal native IDs, independent updates, independent deletion, legacy conflict refusal and reconnection with isolated IDs.
- Deployment requires
pnpm --filter @repo/database migrate:deploy.
File refresh queues and progress — 2026-10-27
- The console forwards full file refresh requests to the source queue. Queued file imports can be promoted to a full refresh; a full request during a running import creates one shared follow-up. Both job types expose per-file progress.
- File and other source enqueues lock the same connector row and validate the organization and index before deduplication. Concurrent regular file requests create one import; an existing source sync also prevents a duplicate file run.
Signed connection URLs without query rewriting — 2026-10-27
- Shared sources and legacy connectors use one credential store for URL query values, legacy inline userinfo and DocSearch start/sitemap URLs. Console reads mask credentials; client-supplied ciphertext is rejected on create and update.
- New authenticated URL envelopes preserve raw value encoding, duplicate keys, parameter order and all other URL bytes. Masked edits retain each original value separately; endpoint changes and ambiguous duplicate removal require fresh credentials. Previous encrypted URLs remain readable and rotatable.
- Plaintext size limits are checked after opening stored envelopes. Runtime diagnostics also mask noncanonical encoded values. A signed HTTP fixture rejects any query-byte change during the real catalog/engine checks.
- Regular file synchronization retains its skip set even though file connector rows use full mode; only an explicit full refresh forces re-extraction.
Current-source retries and stale-record protection — 2026-10-27
- All connector failures, including native CMS and file extraction, retry the current source through a queued full synchronization. Retrying no longer imports an obsolete stored body or a file removed from the current manifest.
- A verified successful full read resolves earlier connector errors in the same owned completion transaction, after deletion cleanup. Partial reads and lost jobs cannot dismiss those errors.
- Manual retries require the latest failure and an existing never-accepted failed document under the index write lock. Accepted, deleted, purged and orphaned source documents require a fresh import or source connection.
- Full file refreshes re-extract unchanged-size files; regular runs retain their skip set. Recovery actions are translated in all five console languages.
CMS synchronization completion and recovery — 2026-10-27
- Native CMS imports use bounded batches with cancellation and worker ownership checks around source requests and writes. A superseded worker cannot advance the source cursor or successful synchronization time.
- Rejected records retain their previous deletion state. Failed engine deletions remain pending and are retried before synchronization can report completion.
- Incomplete reads and rejected writes retain the previous cursor. Full reads do not move a timestamp cursor backwards when the newest source record disappears.
- A disposable WordPress HTTP, PostgreSQL and Typesense integration check covers 1,001 records, worker takeover, cancellation, pending deletions and budget limits.
Connection diagnostics and rotated-key access checks — 2026-10-27
- Connector and connection-check diagnostics mask credentials in signed URLs, encoded query values and Basic authorization headers. Very short credentials use a fixed error detail while retaining the actionable error code.
- Corrected the rotation regression test: an environment-bound successor remains forbidden from organization-wide webhooks. A separate positive test confirms that an unbound successor retains access and its expired predecessor is refused.
Full synchronization follow-ups and cancellation — 2026-10-27
- A full refresh promotes a queued source job or queues a fresh run behind work that has already started. The request no longer disappears into a running delta.
- Resolving earlier mapping errors is fenced by the worker's owned job row. Connector progress reaches completion after deletion cleanup rather than before it.
- The sync dashboard shares the worker's cancellation types and protected phases, exposing cancellation for source checks, connector runs and the other queued jobs. Cancelling a connection check clears its validation state atomically and retains any existing working connection. A cancelled scheduled occurrence advances to its next normal time without recording a source outage or retry backoff.
Explicit source attribute mapping — 2026-10-27
- Source and collection forms map arbitrary attributes to declared index fields, including typed lists, decimal conventions and explicitly defined availability values. Bindings can inherit or replace the source's attribute mapping.
- Schema compatibility and private-field settings are checked before binding, candidate promotion and synchronization. Sensitive public fields and objects require explicit visibility confirmation; previews mask private source paths.
- Malformed records enter a diagnostic queue without storing unselected raw values. Valid records can proceed, while failed mappings prevent deletion of older records. Retry queues a fresh full source read; successful complete reads resolve earlier mapping errors.
- Discovery shows nested fields, observed types and sample uniqueness. Failed mappings retain a clearly marked candidate preview; CSV, XML and YAML previews include the original records. Stable numeric identities retain their identity when their content changes; invalid explicit identifiers are rejected.
Serialized source runs and project suspension — 2026-10-27
- Manual jobs and scheduler runs share a connector lock. A replacement worker waits in the queue while the previous worker finishes, without recording a source outage or consuming an attempt. Lost or cancelled jobs stop before further import and cannot overwrite the replacement job's outcome.
- Queued jobs suppress scheduled duplicates. Paused projects stop source synchronization while their existing search results remain available.
- Disposable PostgreSQL and Typesense checks exercise takeover during a blocked source request, successful queued retry and project suspension.
Verified lexical intent and complete source deletion — 2026-10-27
- Generic document queries retain brand, category and use-case words when the index cannot represent them as structured search. Explicit sorting retains lexical terms when it replaces the proposed ranking expression. Entity removal respects Unicode word boundaries and preserves unsupported negations.
- A complete, explicitly requested full read of an empty source removes only
that source's records. Large deletion sets drain in bounded pages before
synchronization reports success. Cleanup failure keeps the previous freshness
and cursor and exposes
DELETION_PENDING; budget failures exposeBUDGET_EXCEEDED. - Destructive prune commits under the current job lock. Previously pending deletions can finish on a later run without affecting another source's cleanup.
- Added a disposable PostgreSQL/HTTP/Typesense integration check covering source identity, actual indexed results, updates, deletion of 1,001 source records and preservation of published results after a rejected budget operation.
Scoped budgets and leased synchronization — 2026-10-27
- Organization owners can set independent monthly resource and spending limits for an organization and each project. The console shows usage, reservations, remaining capacity, warnings, currency and the start of accounting.
- Search, document ingest, AI and rebuilds reserve resources before work. Failed preflight releases the plan quota; partial imports consume only accepted new documents. Actual wallet debits enforce monetary caps inside the ledger transaction.
- Replayed wallet references do not consume a second budget. Project allocations and the cash basis are recorded with each debit. Provider usage above an AI estimate is retained in project accounting instead of being lost.
- Manual connector runs now return a queued job. Concurrent enqueues for the same connector share its active job. A worker that loses its lease cannot advance source freshness, overwrite the new worker or start another import slice.
- Apply
20261027050000_usage_budgetsalong with the source migrations before startup.
Shared sources and logical collections — 2026-10-27
- Projects now contain independent collections and reusable sources with per-collection mappings. Sources can feed multiple collections; collections can combine sources.
- Connection changes are staged and verified by leased preview jobs before replacing a working connection. Preview does not index documents. Source and binding pause, archive and detach operations preserve published documents.
- Source identities are isolated by default; original record ids and provenance remain in the document mirror. A repeated id in another source cannot overwrite its data.
- Protected feeds support bearer, basic and custom-header credentials; secrets and signed URL parameters are encrypted, redacted and included in key rotation.
- Connector synchronization can run as a queued job. Apply the source/collection and source-identity migrations before starting the updated app.
Projects and synchronization correctness — 2026-10-27
- Organization projects with purpose, languages, audience, seven lifecycle states, readiness checks, index assignment and a localized console in five languages. Existing indexes remain accessible and can be assigned explicitly.
- Project writes check organization membership and revision, and commit their audit atomically. Creation retries preserve identity after later edits.
- Incomplete connector reads report partial completion and preserve freshness. The last successful synchronization is stored independently of later attempts; cancellation prevents the final cursor and freshness update.
- Timestamp cursors compare actual instants across offsets and retain fractional precision. Alias provisioning failures clean up the collection they created.
- Apply the two new additive migrations with
pnpm --filter @repo/database migrate:deploybefore starting the updated application.
Outbound request safety and trace privacy — 2026-10-27
-
URL trace attributes now remove authority credentials (
user:password@host), including URLs without a query string and protocol-relative URLs. Scheme, host, port and path remain available for diagnostics. Existing query/fragment redaction continues to apply before export. -
Guarded outbound requests now use fetch and Agent from the same Undici package, avoiding Node bundled-fetch dispatcher incompatibility while retaining the connection-time DNS rebinding guard.
Outbound requests and trace privacy — 2026-10-27
- Fixed every outbound request made through the SSRF-guarded
safeFetchfailing withfetch failed/invalid onRequestStart methodon Node 22 and 24: the guarded dispatcher comes from theundicipackage (major 8) but was handed to the globalfetch, which Node implements with its own bundled undici (6.x on Node 22, 7.x on Node 24).safeFetchnow pairs the dispatcher withfetchfrom the same package. SSRF checks (shape, allowlist, DNS, connection-time rebinding, redirect credential stripping) stay in force; there is no unguarded fallback. - Fixed the tesseract OCR upload leaving as
text/plainwith body[object FormData]: the form is built from undici'sFormDataso the paired client brand-recognises it. - Webhook deliveries now send an explicit User-Agent,
AACSearch-Webhooks/1.0 (+https://aacsearch.dev). - Trace URL attributes also remove authority credentials (
user:password@host), including URLs without a query string and protocol-relative URLs. - Node is pinned to the active LTS line, 24.x, in
.nvmrc,package.jsonenginesand CI (node-version-file: .nvmrc).
API documentation — 2026-10-27
/api/docsnow serves Scalar with a selector for the public Search v1 contract (/api/v1/openapi.json) and the session-authenticated Console/Auth contract (/api/spec.json).- Fixed console OpenAPI generation: analytics and refresh use
/api/search/organizations/{organizationId}/analyticsand/analytics/refresh, with optionalindexIdfiltering. oRPC procedure names and inputs are unchanged. - Declared the API package's
tsxdevelopment dependency so its OpenAPI generator runs after a normal workspace install. Generation still requires the configured environment.
Platform-owner administration — 2026-10-27
- Added 25 owner report routes, Customer 360, grouped desktop/mobile navigation, 50-row pagination, server-side filters and current-page CSV export. Existing operational controls remain linked from customer, installation, product, billing and security reports.
- Reported CMS versions and storefront stages, safe site origins, effective plan allowances, activity cohorts, AI tokens, process-local HTTP monitoring and read-only engine capacity.
- Corrected owner financial definitions: usage statements are not added to wallet top-ups as cash revenue; unknown cash/costs remain unavailable. Catalog MRR excludes trials and overdue subscriptions, uses provider currency overrides and discloses missing contract quantities.
- See
docs/ADMIN-BUSINESS.mdfor data sources, coverage limits and validation boundaries.
CMS modules — 2026-10-27
-
Bitrix 0.5.2: admin UX from UX-SPEC §4 — 9
CAdminTabControltabs with a status banner, install requirements check + data-processing consent note (step.php), uninstall with keep/drop data (unstep1.php,check_bitrix_sessid, key always cleared, SaaS disconnect best-effort); shopper behaviour gated by visitor consent (track_consent_required,aacsearch_consentcookie / Main\UserConsent);SelfTest,Updater(updater0.5.2.php), canonicalsite_urlin connect payloads,docs/CONNECT-API-CONTRACT.md; portableStorefront::globBrace(noGLOB_BRACE, musl/Alpine). -
Bitrix 0.5.1: hotfix — register
Behaviourininclude.php(search page and scheduler tick no longer fatal before heartbeat); click beacon uses a salted visitor (sessionId/anonymousId, no basket user created) and still salts a legacy int FUSER_ID from oldevent.php; index/key change or disconnect resets behaviour cursors and the embed token. Batch-level/embed/eventswire format unchanged from 0.5.0. Marketplace update: runintegrations/bitrix/aacsearch.search/scripts/pack-marketplace-update.sh 0.5.1to build0.5.1.zip(0.5.1/updater.php,description.ruwindows-1251, changed files;CopyFiles('install/tools', 'tools')→/bitrix/tools/aacsearch/event.php). -
Bitrix 0.5.0: shopper behaviour for the product graph and recommendations (option «Собирать поведение покупателей», off by default). The scheduler tick reads what Bitrix already writes — product views (
b_catalog_viewed_product), carts and deferred items (b_sale_basket), orders (b_sale_order, from 1C and the admin too), the last 90 days on the first run — and sends them to/embed/eventswith a short-lived embed token minted by the module's key, so installed shops need no new key scope. Search-result clicks are sent by the results page (/bitrix/tools/aacsearch/event.php, session-checked). The shopper is a salted hash of the basket user, never a user id, e-mail or IP. New componentaacsearch:recommendations(STRATEGYco_view«Смотрят вместе»,complement«С этим товаром покупают»,substitute,next) for the product page: graph neighbours first, catalog similarity after, products a guest cannot see dropped, cached 10 minutes, nothing shown while recommendations are off for the index. Verified on the Бизнес stand from real storefront requests: views, carts, orders and a search click arrived, the graph rebuild produced co_view / next / complement edges, and the product page shows «Пантолеты Кости на Пляже» as bought together. -
Bitrix 0.4.4: the header search box sends Enter to the AACSearch results page (
/search/) when the site has one; shop templates point the native box at/catalog/, so on Бизнес / Малый бизнес the full results came from the catalog's own search while suggestions came from AACSearch. -
Bitrix 0.4.3: a files-only update (sync, manual copy, Marketplace) now also refreshes the storefront components in
/local/components/aacsearchand/bitrix/tools/aacsearchon the next scheduler tick (files_version); before, they stayed at the version of the last install. The results page no longer offers API-only facets (hierarchy_lvl*, unit, site ids) or a second «Бренд» built from the property the built-in brand already uses; pickup points get a label. -
Bitrix 0.4.2: property writes through
CIBlockElement::SetPropertyValuesEx/SetPropertyValues(1C exchange, imports) reach the index right away (OnAfterIBlockElementSetPropertyValues(Ex)), for products and offers alike; before they waited for the next delta. Create / update / property / deactivate / delete, price, stock, offers, sections, activity dates, consolefull_syncand reconciliation verified end to end on the Бизнес and Старт stands (docs/BITRIX-EDITIONS.md§12). -
Bitrix 0.4.1: search finds what the site's own search finds (
CIBlockElement::UpdateSearch) plus what shoppers filter and type by. Properties the shop marks «Участвует в поиске» go tocontent(when an iblock uses the flag at all; demo / 1C iblocks without flags keep every property), and so do smart-filter, offer-variant, brand and article properties, now also when they are promoted fields. Articles are searchable in latin and cyrillic look-alike spellings (T-100/Т-100) and are never a facet. Flag-like values («false», «Нет», empty GUIDs, colour codes) stay out of the text.tagscarry the section names with their ancestors and the elementTAGS;hierarchy_lvl0..3carry the main section path for hierarchical facets. New sortable signalsbx_sort(SORT),bx_views(SHOW_COUNTER) andbx_orders(placed orders, sale module); views and orders change noTIMESTAMP_X, so they are refreshed by a full sync. -
Bitrix 0.4.1: what the storefront hides stays out of search — elements linked only to deactivated sections are skipped (option «Не искать в отключённых разделах», on by default) and removed by delta and reconciliation; a section switched on or off, renamed or moved re-exports its elements (new
OnBefore/OnAfterIBlockSectionUpdatehandlers, paged by the delta). Elements whoseACTIVE_FROM/ACTIVE_TOpasses appear or expire althoughTIMESTAMP_Xdid not change: the delta pages them by (date, ID), and since those dates have no index it scans only when the nearest known date has come (edits update it) or hourly. 0.4.0 read the dates with filter keys Bitrix ignores and re-sent up to 5 000 elements every delta. Smart-filter properties that the 2 000-element sample missed are kept by an SQL count (385 such properties on Termokit). The settings page marks the iblocks the site's own search covers and names the ones not selected yet. -
Bitrix 0.3.0: facets follow the shop's own catalog setup — properties shown in the smart filter (
b_iblock_section_property, with their display type) and offer-variant properties (OFFER_TREE, read only when property features are on) are promoted first;LIST_PAGE_SHOWadds a small bonus. -
Bitrix 0.3.0: catalog editions (Малый бизнес / Бизнес) export
bx_unit,bx_weight_gandbx_store_stock(active pickup points with stock; never back-office warehouses or addresses);bx_stock_qtyonly with the new «Публиковать точный остаток» option; multi-site installations exportbx_site_ids. Старт / Стандарт are unchanged (no catalog). -
Bitrix 0.3.3: the module has its own admin menu «Сервисы → AACSearch — поиск» (connection and status, sources and fields, site search and schedule) instead of living only in «Настройки модулей».
-
Bitrix 0.3.2: settings page in the admin speaks the admin's language — step times formatted like the rest of the page, run and reconciliation statuses translated, field reasons («справочник + умный фильтр») translated; capabilities show price types, stores, pickup points, smart filter and property features; the analysis summary says when there is no product catalog; service, empty and unsupported properties are folded under «Не используются». Install / uninstall verified on Старт, Стандарт, Малый бизнес, Бизнес and the Termokit shop.
-
Bitrix 0.3.1: store stock changes (
OnStoreProductAdd/OnStoreProductUpdate/OnBeforeStoreProductDelete, which the 1C exchange fires too) queue the product for the next delta run; the delta's catalog and price polling reads inTIMESTAMP_Xorder and resumes from the last row instead of dropping everything past the first 2 000 changed rows; the scheduler tick registers event handlers that a file-copy update did not install. -
Bitrix 0.3.1: after a file-copy update the scheduler re-registers the running module version, so the panel stops offering an update that is already installed; properties that can be neither a field nor an attribute (service, empty, unsupported) are no longer listed in the panel's field table.
-
Console: the integration card names the edition (Малый бизнес, not
small_business) and says what the module found on the site (catalog, offers, smart filter, pickup points, price types, sites). The CMS wizard no longer pre-selects an index that already holds other documents (it proposes the first empty production index or a new one) and proposes Russian as the catalog language for Bitrix / DIAFAN shops. The pull «1C-Bitrix» connector, which needs an export module that is not shipped, is no longer offered for new sources (existing ones keep working). -
Bitrix 0.3.0: capabilities report price types, stores, pickup stores, smart filter and property features (read lazily, not on every element save); the promoted-field budget counts applied fields that are no longer promoted.
Customer journey — 2026-10-26
- One first-run entry for website and API users, with workspace creation and a resumable Data → Check → Connect wizard. Profile/avatar setup is no longer required before using search.
- File import previews and field mapping; scoped API examples; widget installation and external-request verification. Preview traffic does not confirm installation.
- Auth preserves safe local return destinations, displays only configured OAuth providers, and offers email resend/recovery. Required authentication emails propagate delivery failures, including Resend error responses. Stable widget assets revalidate every five minutes.
Widgets and billing — 2026-10-26
- Saved widget loader at
/embed/v1/loader.js: public-id bootstrap, refreshed sessions, search, autocomplete and recommendation mounts; publication and deletion in the console. - Polar: update subscription seats through the installed SDK and pass the initial seat count to checkout.
CMS modules — 2026-10-26
- Bitrix 0.2.1, PrestaShop 2.1.1 and DIAFAN 2.0.1: isolate integration commands, retry acknowledgements, preserve working connections on failed replacement, reset cursors when switching indexes, and enforce current storefront visibility.
- Bitrix: bound large catalog registration reports and document transactions; preserve later theme edits when undoing search wiring. DIAFAN state checkpoints now use atomic upserts. Autocomplete ignores responses superseded by cleared input.
- Deploy script includes the separate Termokit Bitrix stand and bundles the generated PHP SDK.
API changes
Every new, changed, deprecated or removed public route under /api/v1 gets one line here, under
the heading of the API version it ships in, in the same PR as the route (see AGENTS.md). Format:
- **Added|Changed|Deprecated|Removed** `METHOD /path` — what a client has to know.
Breaking changes only ever land under a new major heading (### v2), never under ### v1.
v1
-
Added
GET /embed/widgets/{publicId}— token-free public meta of a hosted widget ({id, type, name, index:{name}, hosted:true, theme:{accent,dark}}); 404 unless the widget is enabled and hosted. IP rate limited (embed tier). -
Added
POST /embed/assistant/stream— the widget assistant on the embed session token (token,question,conversation_id?,client_id?,filter_by?). Same SSE frames asPOST /indexes/{slug}/assistant/stream(citations→delta… →done|error);conversation_idis handed out bound to the index and visitor; 403ASSISTANT_DISABLEDwhen the index has the assistant off; 429 per session token (20/min) and per address (30/min). -
Added
POST /embed/assistant/feedback— rate a widget answer (rating: 1 | -1,message_id,conversation_id,client_id,comment); same write path asPOST /feedback. -
Changed
POST /embed/widget-session— accepts the deployment origin (the hosted page/w/{publicId}) when the widget has hosting enabled, or withpreview: <console session>for a draft share link; the response addswidget.hosted,previewandsettings.assistant(enabled+ experience fields).POST /embed/configadds the samesettings.assistantblock. -
Changed widget config (
widget.config/settingson/embed/widget-session,/embed/config) — new optional keystitleField,urlField,descriptionField,facetTypes(field →list|range|toggle|hierarchy|select|rating|buckets),hierarchyFields[],resultsMode(pagination|infinite),hitsPerPageOptions[],layoutalso acceptslist,template,showClearRefinements,mobileDrawer;facetsmax raised from 8 to 12; recommendationsstrategyacceptscomplement|next|co_view|substitute(the legacypersonalized|related|trending|popularstay valid as aliases). -
Changed
POST /indexes/{slug}/quality/sets/{id}/queries—judgments[].gradeis an integer0..3(wider values are refused with 422, not clamped), at most 200 judgments per query; a set over its query cap answers 422SET_TOO_LARGEwithdetails.max. -
Changed
POST /indexes/{slug}/quality/sets— the created set is returned withqueries: 0, runs: 0(counts, same shape as the list). -
Changed
POST/PUT /indexes/{slug}/rules— aboost/buryaction'sfilter.valueisstring | number | boolean(numbers and booleans are no longer coerced to strings). -
Changed
POST /indexes/{slug}/snapshots/{id}/restore— the worker verifies configuration and document integrity, retains private-field restrictions, and rejects stale publication after concurrent schema or settings edits. -
Changed
POST /indexes— accepts the documentedfieldsandsettingswith the same validation as console creation, including embedding configuration. Externally supplied credential ciphertext is rejected. -
Changed
PATCH /indexes/{slug}/schema— returned plans and rebuild-required error plans mask embedding provider keys and URL credentials; masked schema edits preserve credentials only for the unchanged provider and endpoint. -
Changed
GET /jobs/{jobId}— per-file results are also returned forconnector_syncjobs that perform a full file refresh, matchingfile_import. -
Changed
POST /connect/exchange— missing SaaS / session API origin answers500 API_BASE_URL_NOT_CONFIGURED(neutral public message; was503, which CMS modules retry). Unused ZIP-issued CMS keys are rotated inside the same exchange transaction so reconnects do not pile active keys. -
Added
POST /connect/opened— CMS Marketplace tab marks a connectstateas opened (openedAt; expired →410 CONNECT_EXPIRED, consumed →409 CONNECT_CONSUMED). -
Added
POST /connect/callback— partner callback reveals the one-timecode(or200 {ok,error}forBITRIX_NO_RIGHTSwithout a code); domain mismatch →CONNECT_DOMAIN_MISMATCH. -
Added
POST /connect/verify— Bearer key health after exchange; optionalstatemarks the sessionverified; returns{ok, latency_ms}. -
Added
POST /connect/disconnect— Bearer key revoke + mark push integration disconnected (index documents kept); already-revoked keys →200. -
Added
POST /connect/exchange— CMS module exchanges a one-time connectcode+statefor an index-bound API key (server-to-server; reuse/expiry →CONNECT_CONSUMED/CONNECT_EXPIRED). Optionalreturn_urlon success when the session was created with one. -
Changed
GET /integrations/{platform}/module.zip—mode=code&state=embeds{v:2,url,code,state,index}without a key; a brokenticketanswersCONNECT_TICKET_INVALIDand does not mint a replacement key. -
Changed
POST /indexes/{index}/scoped-key— optional search params (query_by,sort_by,include_fields,exclude_fields,facet_by,per_page,limit_hits,prefix,num_typos,group_by,group_limit) are locked into the scoped key; tenantfilter_by+ private/aclexcludes always win. Private-field refs on those params answerPRIVATE_FIELD. -
Changed Assistant / RAG context — emails, phones and long digit runs in retrieved chunks are replaced with placeholders before the LLM sees them.
-
Added
POST /rules/preview— dry-run a draft merchandising rule against a caller-supplied hit list (with_publishedalso applies live rules); returns hits,redirect,banner,applied_rules. Requiressettings:write. No write. -
Changed
POST /indexes/{index}/events— optionaltags(string[]) andmetadata(string/number/bool map) are stored under the event'smetadata(withapiKeyId); never used for ACL. -
Changed
POST /indexes/{index}/search,GET /indexes/{index}/searchandPOST /embed/search— merchandising may returnredirect,banner({text, url?}) andrules_applied; rules acceptmatch.whenZeroResultsand optionalaction.banner. -
Changed Curation save (REST + console) —
rule.tagstriggers via searchcuration_tags(Typesense curation sets). -
Changed
POST /indexes/{index}/search,GET /indexes/{index}/searchandPOST /embed/search— article codes stay inq: a hyphenated code (144-14-02,T-100-20) or a descending / glued pair (170-70,120-60см) is no longer read as a price range (it becameprice:[144..14]andqwas cut to-02); only a standalone ascending pair (1000-2000,5000 - 9000 руб) is. -
Changed
POST /indexes/{index}/search,GET /indexes/{index}/searchandPOST /embed/search— a code-like query no longer adds a hardexternal_id:=…filter —external_idis the connector's document id, not the article; asku/article/model/part_number/product_codefield still gets the exact filter. -
Changed
POST /recommendations— acceptsseed_document_ids(cart / multi-seed, max 20) walked with the product graph;strategyalso accepts commerce aliasesreplacement/analog/analogue(→ substitute) andaccessory(→ complement). An out-of-stock seed with no strategy/filter auto-selects substitute neighbours filtered byin_stock:=truewhen the schema has a stock field. -
Changed Assistant citations may include
page(1-based) when chunked file hits carry apagefield; LLM calls abort afterassistant.llmTimeoutMs(default 30s) and fall back to extractive answers. -
Added File connector accepts
.xlsx(first sheet → TSV text). NL understanding maps warehouse / lead-time phrases onto schema fields when present. EmbedAACSearch.optOutProfiling(). Console recommendation settings exposeconsentRequiredandpersonalizationImpact. Org overview totals includecoverageRate/rescuedRate. -
Changed Query alternatives retry Cyrillic↔Latin unit spellings (
128гб↔128gb). Negations like «не красный» become hardcolor:!=filters. Optionalsettings.search.distanceThresholdis passed into hybridvector_query. Embed widget rendersguide.blocks(branch / assistant_prompt). -
Added
DELETE /indexes/{index}/subjects/{subjectKey}— DSAR erasure of query logs, click/behaviour events and recent queries for one visitor/user on the index (settings:write, server-side key). Raw ids are hashed withSEARCH_PII_SECRETthe same way as intake. -
Changed
POST /indexes/{index}/assistant,POST /indexes/{index}/assistant/previewandPOST /indexes/{index}/assistant/stream— optionalacl(user:…/group:…/role:…) is applied to retrieval the same way as search: server-side keys only; origin-bound / browser keys stay anonymous so RAG cannot see documents search would hide. -
Changed
POST /recommendationsandPOST /similar— optionalfilter_by(stock, brand, …) is AND-ed onto candidate lookups; the seed document is still loaded without it.strategyalso acceptsbought_withas an alias of behaviouralcomplement(often bought together). Event intake acceptsfavorite; cart and favourite events feed_popularitybetween click and conversion. -
Changed
GET /organization/export— the JSON also includesquery_logsandclick_events(up to 5 000 newest each) so a tenant can take their visitors' search and click history with them; purge viaDELETE /organization/dataalready erased those tables. -
Changed
guideonPOST /indexes/{index}/search,GET /indexes/{index}/searchandPOST /embed/search—guide.routeisquestionorcatalog(a question mark, a leading question word, or Russian «ли»; the caller'sqis not rewritten). A question adds a chip withkind: "assistant". Chips may also includedropFilter(re-search without that filter fragment) andkind.understanding.routeis set when price or commerce understanding ran. -
Added
matchonPOST /indexes/{index}/search,GET /indexes/{index}/searchandPOST /embed/search— how much of the query the first hits reflect:{query, coverage, level, matched, unmatched}.coverageis the share of meaningful query words (Russian and English function words such as «для» / «the» excluded; words turned into a price filter, brand or colour count as found) found in the best of the first five hits;levelisfull,partial(≥ 0.5),weakornone; clients should treatnoneandweakas «nothing found». It describes the text of the hits, not their relevance. Absent for*, queries of function words only, pages after the first and hits returned without highlights (aninclude_fieldsthat leaves out the queried fields). -
Changed zero-result recovery — the
or-tokensanddrop-last-tokensteps no longer treat Russian and English function words as query words: «зарядка для самоката» is no longer answered by every product containing «для», and a query with fewer than two meaningful words skipsor-tokens. -
Added
POST /indexes/{indexSlug}/feed-preview— reads an xml/yaml/csv/json source URL (scopedocuments:write, nothing is indexed) and returns detected fields, a suggested versioned mapping, a mapped sample and the index fields it needs. Connector configs accept an optionalmapping(fixed transforms:strip_html,to_number,to_bool,split,join,default,lowercase,date_to_unix,first_non_empty). -
Changed
GET /organization/export— nested audit metadata also strips internal topology keys, including physical storage names and aliases; customer document titles and URLs are preserved. -
Changed
POST /indexes/{indexSlug}/scoped-key— browser keys are restricted to the returned public handle for one index/environment. Parents are isolated per API key and index; revocation/rotation invalidate every index of that issuer. Shared and v2 wildcard parents are retired on first mint/revoke after upgrade, so older browser keys must be re-minted. -
Changed document imports and edits — writes to one index are serialized across app instances, including revision checks, so parallel imports cannot allocate the same revision or leave the engine and mirror in different write orders. Lock contention past the database wait deadline answers
409 CONFLICT. -
Changed
GET /indexes/{index}/suggest,POST /indexes/suggest/multi,POST /embed/suggest— word and phrase completions include additivecompletionType; validated layout, typo and adjacent-letter corrections are exposed asdid_you_mean, with price constraints preserved on selection. -
Changed search responses expose spelling hints from successful public token matches too; layout recovery reports the query that actually found hits.
-
Added
POST /embed/suggest— token-scoped, rate-limited autocomplete; returns document completions and recognized price filters without consuming search quota. -
Changed
GET /indexes/{index}/suggest— additiveactive_filtersandcompletionQuerypreserve recognized prices when a completion is selected; suffix/infix completions use only fields indexed withinfix: true. -
Changed
POST /indexes/multi_search— federated multi-search is not index-scoped: the indexes are named in the body, andPOST /indexes/{index}/multi_searchanswers 404. The generated SDKs call the unscoped path. -
Changed
POST /indexes/{index}/reindex— answers202 {index, job_id, status: "queued", status_url}instead of blocking; pollGET /jobs/{jobId}.409 JOB_CONFLICT(withdetails.job_id) while a rebuild is already queued or running.?sync=truekeeps the old blocking200(documents,duration_ms) for test suites only. -
Changed
POST /indexes/{index}/docsearch/preview— queues acrawljob and answers202; the pages, errors and first 25 records are injob.resultofGET /jobs/{jobId}oncecompleted. -
Added
GET /jobs/{jobId}— status,phase,progress(0–100),resultof a reindex, crawl, file import, snapshot or restore job of the key's organization (another tenant's id is404 JOB_NOT_FOUND). Needs the scope that started the job. -
Added
DELETE /jobs/{jobId}— cancels a job before the alias swap;409 JOB_NOT_CANCELLABLEwhen it finished or is already swapping. -
Added
POST /api-keys/{keyId}/rotate—settings:write; body{grace_days?}(default 7, max 30);201with the successor's plaintextkey(shown once) andprevious_key.valid_until.409 KEY_ALREADY_ROTATED/KEY_NOT_ACTIVE. A successor never gets scopes the caller lacks. -
Changed authentication — a key superseded by a rotation whose grace has ended answers
401 KEY_ROTATED(wasUNAUTHORIZED). -
Changed
/admin/*(GET/DELETE /admin/snapshots,POST /admin/snapshot,POST /admin/jobs/run) — requiresx-platform-admin-tokenequal toSEARCH_PLATFORM_ADMIN_TOKENin addition to the API key;403 FORBIDDENotherwise, and the surface is closed when the secret is unset. Every call (allowed or denied) is audited. -
Changed
POST /admin/snapshot—503 SNAPSHOT_NOT_CONFIGUREDwhenSEARCH_SNAPSHOT_DIRis not set (an absolute path on the engine's filesystem). -
Changed
GET /metrics— Prometheus text exposition 0.0.4 withAccept: text/plainor?format=prometheus; JSON stays the default (new keysgauges.ingest_jobs,cache). The token may also be sent asAuthorization: Bearer. -
Changed
GET /status— throttled per client address like/health(429). -
Added
POST /indexes/{index}/snapshots—settings:write+documents:write;202 {job_id, snapshot_id, status, pruned}.409 SNAPSHOT_LIMIT_REACHEDbeyond the plan'ssnapshotsPerIndexunless?prune=true. -
Added
GET /indexes/{index}/snapshots— complete snapshots, newest first (documents:read). -
Added
GET /indexes/{index}/snapshots/{snapshotId}— manifest plus a 15-minute signed download URL for the gzipped NDJSON (documents:read);404 SNAPSHOT_NOT_FOUND. -
Added
DELETE /indexes/{index}/snapshots/{snapshotId}— both write scopes. -
Added
POST /indexes/{index}/snapshots/{snapshotId}/restore—202 {job_id}; new collection then alias swap.409 JOB_CONFLICTwhile a reindex or another restore is queued or running. -
Added
GET /billing/invoices—billing:read;?limit1–100; monthly usage invoices. -
Added
GET /billing/invoices/{id}— one invoice as JSON;{id}.pdfreturns the PDF. -
Added
POST /billing/invoices/generate— platform admin only (x-platform-admin-token);?period=YYYY-MM(default: last closed month) and?organization_id. Idempotent. -
Changed search responses (
GET/POST /indexes/{index}/search) — newrerankfield ("applied"or"skipped:<reason>") when the index has a reranker;explainobject forexplain=true(incl.explain.rerank);hit_reasons[].reasons[].kindgainspopularityandrerank. -
Changed private fields — fields in
settings.search.privateFieldsare removed from hits, highlights, facets,GET /documents/{id}and/documents/exportunless the key has the new scopedocuments:read_private; referencing one inquery_by,filter_by,sort_by,facet_by,include_fieldsorgroup_bywithout it is400 PRIVATE_FIELD. Embed requests never see them. -
Changed
POST /indexes/{index}/documents— response may carrynormalizationErrors(per field: values a schemanormalizerule for date / price / geo could not parse; written untouched). -
Changed
POST /indexes/{index}/clone— body acceptsenvironment(production|staging|development, default: the source's); the response echoesenvironment. A key bound to another environment gets403. -
Changed every index-scoped route — a key bound to an environment answers
404for an index of another environment (deliberately not403). -
Changed
POST /indexes/{index}/scoped-key— refused with400 PRIVATE_FIELDS_PRESENTfor an index withsettings.search.privateFieldsunless the issuing key hasdocuments:read_private(the engine ANDs the embedded filter with the browser's, sofoundwould be an oracle on private values). A key in its rotation grace window gets403 KEY_IN_GRACE;expires_inis capped at the parent key's remaining validity. Scoped keys are not revoked with their parent. -
Changed
PUT/DELETE /indexes/{index}/presets/{id}— rate limited per key (429,x-ratelimit-*headers) like the other write routes. -
Changed
GET /billing/usage,GET /billing/wallet— documented: requirebilling:read(403), rate limited per key (429). -
Changed
GET /indexes/{index}/snapshots/{snapshotId}— documented:downloadisnullwith anoteunless the key hasdocuments:read_private. -
Changed
/webhooks/*— documented:403for a key bound to an index or an environment. -
Changed
POST /api-keys/{keyId}/rotate— a successor never widens the caller:403 KEY_ENVIRONMENT_MISMATCHwhen an environment-bound key rotates a key of another environment,403 KEY_LIFETIME_EXCEEDS_CALLERwhen the target key (or its successor) would outlive the calling key,403 FORBIDDENfor a target allowed on origins the caller is not,403 KEY_IN_GRACEwhen the calling key is itself in a rotation grace window. The successor keeps the environment of the key it replaces (aac_test_staysaac_test_). -
Changed authentication — revoking a key in the console also ends its rotation grace window: the next request is
401. -
Changed
GET /organization/export,DELETE /organization/data— requiresettings:write;403for a key bound to an index or an environment (both act on every index of every environment). The export also refuses origin-restricted (browser) keys; the purge with?documents=trueadditionally needsdocuments:write. -
Changed
POST /indexes/multi_search— everysearches[].indexis resolved like a single search: in the key's environment (another environment's index is404) and, for an index-bound key, only its own index (403). Previously a slug present in several environments resolved to an arbitrary one. -
Changed
filter_by(search, multi-search, facet search, assistant) —&&and||are accepted (were400 INVALID_QUERY).facetFiltersare merged as(<filter_by>) && (<facets>), soa || bplus a facet now constrains both operands. The private-field check runs on the merged filter and on trimmed / quoted / negated field names (" cost_price:10"is400 PRIVATE_FIELD). -
Changed
GET/POST /indexes/{index}/facets/{attribute}/search— applies the document ACL like search (principals only for server-side keys) and refuses a privateattribute,filter_byorfacetFilterswith400 PRIVATE_FIELDunless the key hasdocuments:read_private. -
Changed
POST /indexes/{index}/assistant/native— applies the document ACL; private fields are never sent to the model or returned, and naming one infilter_by/query_byis400 PRIVATE_FIELD. -
Changed search quota — a search that fails after its unit was reserved (engine error,
PRIVATE_FIELD, rerank or rules failure) never consumes the unit. -
Changed search result cache — changing a merchandising rule invalidates cached responses of the index (the rules version is part of the cache key).
-
Changed
POST /quality/sets/{setId}/run— queues anevaljob and answers202 {run_id, job_id, status: "queued", status_url}; metrics appear inGET /quality/runs/{runId}once the run is done.?sync=truekeeps the old blocking answer withmetrics. -
Changed
POST /indexes/{index}/reembed— one batch per call (?batch=, default 20, max 200, was a hard 20) with an opaquecursorto pass back as?cursor=untiltruncatedis false;?all=truequeues areembedjob for the whole index (202 {job_id, status_url}),409 JOB_CONFLICTwhile one is active,501when the engine has no auto-embedding. -
Changed
GET/DELETE /jobs/{jobId}— new job typesevalandreembed(both read and cancelled withsettings:write);file_importresultgainsocr_pages; a key bound to an environment gets404 JOB_NOT_FOUNDfor jobs of other environments. Cancel effect by type: reindex / restore drop the half-built collection, snapshot publishes nothing, file import and reembed stop between files / batches (work already written stays), a crawl's result is discarded, an eval run is closed as failed. -
Changed
POST /indexes/{index}/reindex—409 JOB_CONFLICTalso while a snapshot restore is queued or running, and the same check now applies to?sync=true(withdetails.type), which also rebuilds in the index's own environment (a staging rebuild no longer touches production). -
Changed
POST /indexes/{index}/documentsand every write path — ametadatavalue that is already a JSON string is stored as is (restores and reindexes no longer double-encode it);_popularity/graph_*in a client payload are still dropped, and their server values survive full re-upserts, reindex and snapshot restore. -
Changed
GET /billing/usage—documentscounts production indexes only; staging and development documents are neither capped byincluded.documentsnor billed as overage. -
Changed
GET /billing/invoices*—overagelines come from the period's own wallet settlements, priced with the plan and block price recorded at settlement (not today's plan); blocks the wallet could not pay for appear as a separate, uncollectedoverageline.
Unreleased
Tooling
- Added
@shadcn/lint(oxlintjsPlugins) with warn-levelno-raw-colors/no-arbitrary-values, pluspnpm lint:designratcheting max-warnings forapps/saas+packages/ui. Playwright a11y smoke (apps/saas/tests/a11y-shells.spec.ts) covers login/signup shells (desktop/mobile, light/dark, keyboard focus, no horizontal overflow, axe serious/critical). Seedocs/development/design-lint.md.
API
- Changed
POST /embed/events— each event may carryoccurred_at,session_id,visit_idandevent_key. Historicaloccurred_at/ per-event visitor /event_keyrequire an embed session minted withbackfill: true(events:backfillscope, server-side API-key mint without browserOrigin). Ordinary page tokens use server time, ignore clientevent_key, and store hashed session/visit ids. Events older than 90 days or in the future are dropped (not re-stamped). Oversizedvalue_centsare clamped; invalid events in a batch are listed inrejected(201) so a CMS cursor can advance; write failures answer 503. The OpenAPI contract keeps the typed event item schema forevents[](the handler parses the batch leniently and validates per item), and documents the 201 body (recorded,rejected[]) and 503. - Changed
POST /embed/session— optionalbackfill: trueaddsevents:backfillto the token scope; refused withBACKFILL_NOT_ALLOWEDwhen the request carries a browserOrigin.
CMS modules
- Bitrix 0.5.3: shopper-behaviour rewrite for the product graph — cursor-ordered pagination; per-event
wire()withsession_id/visit_id/occurred_at/ site-prefixedevent_key({SITE_ID}:view|cart|order:…,view:ID:TSfor repeat views); emptyquery_idomits the click key;value_centsclamped to 100_000_000; embed session minted withbackfill: true; cursor advances only after/embed/eventsaccepts the batch; 429 pauses collection; send window 85 days;rememberShown/ recommendations breaker. Keeps the 0.5.2 consent gate (shopEnabled()+visitorConsentGranted()before any FUSER hashing;enabled()requires an interactive hit with consent whiletrack_consent_required) and the portableStorefront::globBracefrom #11/#26. Marketplace update:install/updater0.5.3.phpnext to the 0.5.2 one:B_PROLOG_INCLUDEDguard; loadUpdaterfrom archive__DIR__/lib/Updater.phpthen the installed module; fail with$updater->errorMessage/ exception + Log when missing (no silentreturn). The 0.5.3 pack file list is a follow-up;pack-marketplace-update.shis unchanged.
Search corrections and admin usability
- Reviewed all 55 SaaS page owners and walked their URLs in the local browser;
route coverage and validation boundaries are recorded in
docs/LOCALHOST-3000-AUDIT.md. - Fixed language persistence, malformed onboarding steps and premature OTP validation; added localized accessible labels across account controls.
- Restricted organization logo upload signing and organization mutation controls to their actual permissions. Subscription cancellation on account/organization deletion now runs in authorized Better Auth lifecycle hooks.
- Added page loading/error recovery, an expected unavailable-invitation state, environment-correct analytics links and safe document/citation URLs.
- Added labeled word/phrase completions and selectable spelling hints in the console and embed widget. Keyboard-layout recovery supports phrases, mixed correctly typed words, short words and model numbers without rewriting the caller's primary query. Price and visibility filters stay on recovery probes.
- Fixed duplicate sidebar keys for Overview/More; groups can be expanded and collapsed, with an active-page marker and larger mobile controls.
- Fixed trailing search debounce and URL pagination in the user/organization admin lists. Request errors have retry actions, unavailable organizations cannot become create forms, and renaming keeps existing organization URLs.
- Added safe wallet input validation, selected-period semantics, URL search persistence and refresh feedback to platform Search ops.
Console structure and analytics
-
Search inputs now show prefix/infix completions and removable price chips; selecting a completion preserves the price. New/rebuilt collections index
titlefor infix matching; older collections need a rebuild for suffix matches. -
Changing a field's infix option is treated as a rebuild instead of a no-op.
-
Index pages share one tenant/environment server guard; overview data and schema role hints are served by protected oRPC procedures.
-
Index tabs and the sidebar share one navigation registry, preserve nested environment links and select the canonical organization dashboard.
-
/adminconsistently opens Search ops; its charts share the dashboard period and refresh action. Latency cards show window p95, not the maximum daily p95. -
Invariants now reject direct search-engine imports from console routes/modules.
2026-10-25 — bug and security fixes
Console, ingest and operator side of the fixes; REST-visible changes are listed under "API changes → v1" above.
- SSRF —
safeFetchjudges IPv6 forms with an embedded IPv4 (mapped, compat, SIIT, NAT64, 6to4, Teredo), pins the socket to the checked address (undiciAgent, guardedconnect.lookup), stripsauthorization/cookie/proxy-authorizationand caller credential headers once a redirect leaves the original origin, and refuses a cross-origin 307/308 of a request with a body.SEARCH_ALLOW_PRIVATE_URLSis ignored in production unlessSEARCH_ALLOW_PRIVATE_URLS_IN_PRODUCTION=true. - Connector credentials — changing a connector's endpoint without re-entering its stored
secrets fails with
CREDENTIALS_REQUIRED; a secret value starting withenc:is refused withINVALID_SECRET_VALUE. - Secrets key — key chain
SEARCH_SECRETS_KEY(current) /SEARCH_SECRETS_KEY_PREVIOUS(decrypt-only) / legacySEARCH_WEBHOOK_SECRET_KEY; a set-but-malformed key (not exactly 32 bytes) fails withSECRETS_KEY_INVALID; re-encryption withpnpm --filter @repo/api secrets:rotate(webhooks, connectors, SSO client secrets; idempotent,--dry-run). Runbook:docs/RUNBOOKS.md§6. - SSO — a domain is verified only by DNS:
_aacsearch-challenge.<domain> TXT "aacsearch-domain-verification=<token>"(requestSsoDomainVerification/verifySsoDomain, owner only); new or re-domained providers start unverified and cannot sign in until then. Organization slugs that collide with auth provider ids (google,github,credential, …) cannot be SSO provider ids (SSO_PROVIDER_ID_RESERVED). Runbook:docs/RUNBOOKS.md§7. - Field-level security — members (not owner/admin) get console documents without private fields.
- Jobs — rebuild invariants: environment required by
reindexIndex,assertNoActiveRebuildon every blocking path (sync reindex, breaking schema migration →409), shadow name fromresolveShadowCollection, alias-guardeddropShadowCollection, mirror delta and hard deletes replayed before the swap; unleasedrunningjobs failed afterSEARCH_UNLEASED_JOB_MAX_RUNTIME_MS(6 h). - Billing — overage ledger references carry the organization and are unique per wallet
(migration
20261026030000_billing_fixes; before, a second organization's block N was treated as a replay and never charged); dunning ignores out-of-order and replayed provider events (event id and time per provider), resolves the organization from the stored purchase first, and only reacts to the purchase that actually decides the plan; quota notices fordocumentsre-arm only when usage drops below the threshold. - Ingest — opt-in OCR for scanned PDFs (
settings.search.ocr,httportesseract-server, compose profileocr);settings.search.languageFields(per-language stemmed copies); DOCX zip-bomb guard (ARCHIVE_TOO_LARGE) and extraction time budget (EXTRACT_TIMEOUT, 60 s, plus 5 min for OCR). - Known limits — see
docs/ARCHITECTURE.md§11 anddocs/LAYERS-GAP-BACKLOG.md: the SSO plugin calls IdP endpoints at sign-in without the SSRF guard; providers saved before DNS verification keepdomainVerified = trueuntil reset (owner decision pending); scoped keys are not revoked with their parent; Lemon Squeezy has no event id (composite id used); the Creem envelope is taken from its docs, not from a live delivery.
2026-10-25 — layers backlog (docs/LAYERS-GAP-BACKLOG.md, 31 tasks)
By layer of the 25-layer model:
- Perimeter (2) —
docs/DEPLOYMENT.md,infra/Caddyfile,infra/docker-compose.prod.yml;/healthand/statusnever leak engine host/port (perimeter-privacy.test.ts). - Auth (4) — OIDC SSO per organization (better-auth
sso, enterprise plan, consolesettings/sso, local Keycloak underinfra/keycloak); platform-admin token for/admin/*. - Tenants (5) — index environments
production/staging/developmentwith environment-bound API keys (aac_live_/aac_test_), separatenonProductionIndexescap. - Caching (10) — Typesense
use_cache+ Redis result cache keyed by index revision (settings.search.cacheTtl, default 60 s), 30 s in-process config cache, cache hit/miss counters in/metricsand tenant analytics. - ML (13) — optional cross-encoder rerank of page 1 over HTTP; click popularity
_popularityrolled up daily and usable as a profile tie-break (popularityBoost);SearchIndex.embedModel/embedModelVersionwith a config-audit warning on mismatch. - Document processing (15) —
fileconnector (PDF / DOCX / TXT / MD via signed upload), document language_lang(facetable), ingest normalization of date / price / geo by schema. - Reindexing (17) — reindex and DocSearch crawl run as scheduler jobs (
202+/jobs), abandoned rebuilds recovered after a crash. - Index management (18) — tenant snapshots and restore per index (MinIO bucket
index-snapshots, plan retention). - Billing (20) — monthly usage invoices with PDF, dunning (
active→past_due→suspendedafterBILLING_GRACE_DAYS, default 7), 80 % / 100 % quota notifications. - Data security (21) — connector secrets encrypted at rest (AES-256-GCM,
enc:v1:), API-key rotation with grace and expiry reminders 7 days ahead, audit log page in the console (settings/audit, CSV export), field-level security (privateFields). - Observability (22) — OpenTelemetry spans per search stage (
OTEL_EXPORTER_OTLP_ENDPOINT), Prometheus/metrics, optional Sentry viaSENTRY_DSN, alert rules ininfra/alerts.yml. - Documentation (25) — docs changelog page,
docs/RUNBOOKS.md, CIpnpm sdk:check.
Known limits are listed in docs/LAYERS-GAP-BACKLOG.md → "Residual gaps after implementation".
Search
- Smart suggest: document prefixes, tenant-popular completions, typo fallback,
did_you_mean,<mark>highlights, 30s per-tenant cache, ETag/304, multi-index endpoint. - Semantic and hybrid search via OpenAI embeddings (
semantic=true,alpha). - Natural-language search, native RAG, in-app RAG with citations and SSE streaming.
- Voice search (Whisper) and image search (vision → query).
- Union multi-search merged and re-ranked server-side.
Platform
- Multi-tenant API keys with scopes, index binding, allowed origins, expiry and rotation.
- Quotas, wallet overage billing, audit log, zero-result dashboard alerts.
- Customer dashboard with KPI cards, charts, top/no-result queries, per-index table, plan meters.
- Signed webhook ingestion, GDPR export/erasure, CSV analytics, slow-query report, on-demand jobs endpoint and engine snapshots.
Developer experience
- OpenAPI 3.1 document, generated TypeScript and Python SDKs, Postman collection, load-test harness, and 94+ end-to-end checks.