-- allium: 1 -- bDS Full-Text Search -- Scope: core (Wave 1 — in-app full-text search with Snowball stemmers) -- Distilled from: src/main/engine/PostEngine.ts (FTS methods), -- MediaEngine.ts (FTS methods), stemmer.ts use "./post.allium" as post use "./media.allium" as media surface SearchControlSurface { facing _: SearchOperator provides: SearchPostsRequested(query, filters) SearchMediaRequested(query) } surface SearchIndexRuntimeSurface { facing _: SearchRuntime provides: SearchIndexUpdated(post) SearchIndexUpdated(media) } value StemmerLanguage { -- Snowball stemmers for 24 languages -- ISO 639-1 to Snowball mapping -- Applied to both indexing and query processing code: String } surface StemmerLanguageSurface { context language: StemmerLanguage exposes: language.code } entity PostSearchIndex { -- Full-text index projection -- Indexed fields: title, excerpt, content, tags, categories -- Plus all translation titles, excerpts, and content post: post/Post stemmed_content: String } entity MediaSearchIndex { -- Full-text index projection -- Indexed fields: title, alt, caption, original_name, tags -- Plus all translation titles, alts, and captions media: media/Media stemmed_content: String } invariant CrossLanguageStemming { -- Search index uses Snowball stemmer matched to content language -- A post in German is stemmed with the German stemmer -- Translations are stemmed with their respective language stemmers -- Query-time stemming matches the index language } rule SearchPosts { when: SearchPostsRequested(query, filters) -- Full-text search with optional filters: -- status, tags, categories, language, missingTranslationLanguage, -- year, month, date range (from/to) -- Returns paginated results with total count let stemmed_query = stem(query, detect_language(query)) let matched = search_fts(PostSearchIndex, stemmed_query, filters) ensures: SearchResults( posts: matched, total: matched.count, offset: filters.offset, limit: filters.limit ) } rule SearchMedia { when: SearchMediaRequested(query) let stemmed_query = stem(query, detect_language(query)) let matched = search_fts(MediaSearchIndex, stemmed_query) ensures: SearchResults( media: matched ) } rule IndexPost { when: SearchIndexUpdated(post) -- Stems: title + excerpt + content + tags + categories -- Plus all translations' title + excerpt + content let all_text = concat_post_text(post) -- Concatenates: post.title, post.excerpt, post.content, -- join(post.tags, " "), join(post.categories, " "), -- and all translations' title, excerpt, content let index_entry = PostSearchIndex{post: post} ensures: if exists index_entry: index_entry.stemmed_content = stem(all_text) else: PostSearchIndex.created(post: post, stemmed_content: stem(all_text)) } rule IndexMedia { when: SearchIndexUpdated(media) -- Stems: title + alt + caption + original_name + tags -- Plus all translations' title, alt, caption let all_text = concat_media_text(media) -- Concatenates: media.title, media.alt, media.caption, -- media.original_name, join(media.tags, " "), -- and all translations' title, alt, caption let index_entry = MediaSearchIndex{media: media} ensures: if exists index_entry: index_entry.stemmed_content = stem(all_text) else: MediaSearchIndex.created(media: media, stemmed_content: stem(all_text)) }