chore: more complete spec and more aligned with plan
This commit is contained in:
@@ -20,11 +20,11 @@ surface UserAction {
|
|||||||
-- ─── AI operation gating ───────────────────────────────
|
-- ─── AI operation gating ───────────────────────────────
|
||||||
|
|
||||||
invariant AIOperationGating {
|
invariant AIOperationGating {
|
||||||
-- All AI operations check airplane/offline mode before executing.
|
-- All AI operations route through the active endpoint for the current mode.
|
||||||
-- If offline mode enabled:
|
-- See ai.allium AirplaneModeGating for endpoint selection logic.
|
||||||
-- If local provider configured (Ollama/LM Studio): route to local model
|
-- If airplane_mode: use airplane endpoint (local model, e.g. Ollama/LM Studio)
|
||||||
-- Else: show toast "AI unavailable in offline mode", abort operation
|
-- If online: use online endpoint (cloud provider)
|
||||||
-- If online: use configured provider endpoint
|
-- If active endpoint not configured: show toast, abort operation
|
||||||
-- Two model categories:
|
-- Two model categories:
|
||||||
-- Title model: text analysis (suggestions, translation, language detect)
|
-- Title model: text analysis (suggestions, translation, language detect)
|
||||||
-- Image model: vision-capable (image analysis only)
|
-- Image model: vision-capable (image analysis only)
|
||||||
|
|||||||
@@ -46,49 +46,47 @@ entity ChatMessage {
|
|||||||
|
|
||||||
-- One-shot AI tasks (core scope, no streaming)
|
-- One-shot AI tasks (core scope, no streaming)
|
||||||
-- All use OpenAI Chat Completions wire format.
|
-- All use OpenAI Chat Completions wire format.
|
||||||
-- When airplane_mode: use airplane endpoint. When online: use online endpoint.
|
-- Endpoint routing: see AirplaneModeGating invariant below.
|
||||||
-- When no endpoint configured for current mode: disable AI, show toast.
|
-- When no endpoint configured for current mode: disable AI, show toast.
|
||||||
|
|
||||||
rule AnalyzeTaxonomy {
|
rule AnalyzeTaxonomy {
|
||||||
when: AnalyzeTaxonomyRequested(post)
|
when: AnalyzeTaxonomyRequested(post)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
-- All AI activities gated by offline mode
|
|
||||||
-- Suggests tags and categories for a post
|
-- Suggests tags and categories for a post
|
||||||
ensures: TaxonomySuggestion(tags, categories)
|
ensures: TaxonomySuggestion(tags, categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule AnalyzeImage {
|
rule AnalyzeImage {
|
||||||
when: AnalyzeImageRequested(media)
|
when: AnalyzeImageRequested(media)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
requires: is_image(media.mime_type)
|
requires: is_image(media.mime_type)
|
||||||
-- Checks mime type is an image type
|
|
||||||
-- Vision model generates alt text and caption
|
-- Vision model generates alt text and caption
|
||||||
ensures: ImageAnalysisResult(alt, caption)
|
ensures: ImageAnalysisResult(alt, caption)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule AnalyzePost {
|
rule AnalyzePost {
|
||||||
when: AnalyzePostRequested(post)
|
when: AnalyzePostRequested(post)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
-- Generates title, excerpt, slug suggestions
|
-- Generates title, excerpt, slug suggestions
|
||||||
ensures: PostAnalysisResult(title, excerpt, slug)
|
ensures: PostAnalysisResult(title, excerpt, slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule DetectLanguage {
|
rule DetectLanguage {
|
||||||
when: DetectLanguageRequested(text)
|
when: DetectLanguageRequested(text)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
ensures: LanguageDetectionResult(language_code)
|
ensures: LanguageDetectionResult(language_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule TranslatePost {
|
rule TranslatePost {
|
||||||
when: TranslatePostRequested(post, target_language)
|
when: TranslatePostRequested(post, target_language)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
-- Translates title, excerpt, content to target language
|
-- Translates title, excerpt, content to target language
|
||||||
ensures: TranslationResult(title, excerpt, content)
|
ensures: TranslationResult(title, excerpt, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule TranslateMedia {
|
rule TranslateMedia {
|
||||||
when: TranslateMediaRequested(media, target_language)
|
when: TranslateMediaRequested(media, target_language)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
-- Translates title, alt, caption to target language
|
-- Translates title, alt, caption to target language
|
||||||
ensures: MediaTranslationResult(title, alt, caption)
|
ensures: MediaTranslationResult(title, alt, caption)
|
||||||
}
|
}
|
||||||
@@ -102,7 +100,7 @@ rule StartChat {
|
|||||||
|
|
||||||
rule SendChatMessage {
|
rule SendChatMessage {
|
||||||
when: SendChatMessageRequested(conversation, content)
|
when: SendChatMessageRequested(conversation, content)
|
||||||
requires: not airplane_mode
|
requires: active_endpoint_configured
|
||||||
ensures: ChatMessage.created(conversation: conversation, role: user, content: content)
|
ensures: ChatMessage.created(conversation: conversation, role: user, content: content)
|
||||||
ensures: AiStreamingResponse(conversation)
|
ensures: AiStreamingResponse(conversation)
|
||||||
-- AI SDK v6 streamText() with tool-call loop (max 10 rounds)
|
-- AI SDK v6 streamText() with tool-call loop (max 10 rounds)
|
||||||
@@ -120,11 +118,13 @@ rule RefreshModelCatalog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
invariant AirplaneModeGating {
|
invariant AirplaneModeGating {
|
||||||
-- All AI activities must be gated by airplane (offline) mode
|
-- Endpoint routing based on airplane (offline) mode:
|
||||||
-- When airplane_mode = true: only the airplane endpoint is used
|
-- airplane_mode = true -> use airplane endpoint (local model)
|
||||||
-- When airplane_mode = false: the online endpoint is used
|
-- airplane_mode = false -> use online endpoint (cloud provider)
|
||||||
-- If the active endpoint is not configured: AI is unavailable,
|
-- active_endpoint_configured = true iff the endpoint for the
|
||||||
-- show toast notification to user
|
-- current mode has a non-empty url (and api_key for online).
|
||||||
|
-- When active endpoint is not configured: AI is unavailable,
|
||||||
|
-- show toast "AI unavailable — configure {online|airplane} endpoint in Settings"
|
||||||
}
|
}
|
||||||
|
|
||||||
invariant TwoEndpointModel {
|
invariant TwoEndpointModel {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ use "./metadata_diff.allium" as metadata_diff -- DB/filesystem diff and rebuil
|
|||||||
--
|
--
|
||||||
-- MAY change intentionally:
|
-- MAY change intentionally:
|
||||||
-- Implementation language (TS -> Rust), editor (WYSIWYG -> plain text + preview),
|
-- Implementation language (TS -> Rust), editor (WYSIWYG -> plain text + preview),
|
||||||
-- scripting runtime (Python -> Lua), process model (Electron -> native),
|
-- scripting runtime (Lua), process model (Electron -> native),
|
||||||
-- UI framework (React -> Iced)
|
-- UI framework (React -> Iced)
|
||||||
|
|
||||||
-- Resolved questions:
|
-- Resolved questions:
|
||||||
|
|||||||
@@ -37,18 +37,18 @@ surface User {
|
|||||||
-- Add Category: text input + Add button. Reset to Defaults button
|
-- Add Category: text input + Add button. Reset to Defaults button
|
||||||
|
|
||||||
-- 4. AI Assistant Settings:
|
-- 4. AI Assistant Settings:
|
||||||
-- API keys: OpenCode, Mistral (masked display + Change, or password input + Save)
|
-- Two-endpoint configuration (see ai.allium TwoEndpointModel):
|
||||||
-- Local providers: Ollama enable checkbox + capabilities table,
|
-- Online endpoint: URL (text), API Key (masked + Change/Save), Model (select)
|
||||||
-- LM Studio enable checkbox + capabilities table
|
-- Airplane endpoint: URL (text), Model (select)
|
||||||
-- Offline mode: enable checkbox (disabled if no local providers),
|
-- Both use OpenAI Chat Completions wire format.
|
||||||
-- selectors for chat/title/image models (local only)
|
-- Refresh Models button per endpoint: fetches model list from endpoint URL
|
||||||
-- Default Model (select with optgroup by provider + refresh),
|
-- Title Model (select from active endpoint models)
|
||||||
-- shows: max output tokens, context window, pricing
|
-- Image Analysis Model (select, vision-capable only)
|
||||||
-- Title Model (select), Image Analysis Model (vision-capable only)
|
-- Offline mode toggle: switches between online and airplane endpoint
|
||||||
|
-- Disabled if airplane endpoint URL is not configured
|
||||||
-- System Prompt (textarea 12 rows + Save + Reset to Default)
|
-- System Prompt (textarea 12 rows + Save + Reset to Default)
|
||||||
|
|
||||||
-- 5. Technology Settings:
|
-- 5. Technology Settings:
|
||||||
-- Python Runtime Mode (select: Web Worker/Main Thread)
|
|
||||||
-- Semantic Similarity (checkbox)
|
-- Semantic Similarity (checkbox)
|
||||||
|
|
||||||
-- 6. Publishing Settings (SSH):
|
-- 6. Publishing Settings (SSH):
|
||||||
@@ -69,20 +69,15 @@ surface User {
|
|||||||
-- ─── Settings view actions ──────────────────────────────────
|
-- ─── Settings view actions ──────────────────────────────────
|
||||||
|
|
||||||
-- API key validation:
|
-- API key validation:
|
||||||
-- On Save: test API call to provider endpoint before persisting
|
-- On Save: test API call to online endpoint before persisting
|
||||||
-- On validation failure: toast error message, key not saved
|
-- On validation failure: toast error message, key not saved
|
||||||
-- On success: key saved, masked display shown
|
-- On success: key encrypted via SecureKeyStore, masked display shown
|
||||||
|
|
||||||
-- Local provider enable/disable:
|
-- Endpoint configuration:
|
||||||
-- Enabling Ollama/LM Studio: fetches model list, shows capabilities table
|
-- URL field required for both endpoints
|
||||||
-- Capabilities table columns: model name, chat, title, image (checkmarks)
|
-- Refresh Models button fetches model list from endpoint URL
|
||||||
-- Models auto-fetched on enable; manual refresh via Refresh Models button
|
-- Model selector populated from fetched model list
|
||||||
|
-- Per-model info: max output tokens, context window (when available)
|
||||||
-- Model catalog refresh:
|
|
||||||
-- Refresh button next to Default Model selector
|
|
||||||
-- Fetches available models from all enabled providers
|
|
||||||
-- Updates optgroup dropdown with provider groupings
|
|
||||||
-- Per-model info: max output tokens, context window, pricing
|
|
||||||
|
|
||||||
-- Rebuild operations (Data Maintenance section):
|
-- Rebuild operations (Data Maintenance section):
|
||||||
-- 6 buttons, each executes immediately (no confirmation dialog)
|
-- 6 buttons, each executes immediately (no confirmation dialog)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ value TemplateFrontmatter {
|
|||||||
id: String -- UUID v4
|
id: String -- UUID v4
|
||||||
slug: String
|
slug: String
|
||||||
title: String
|
title: String
|
||||||
kind: post | list | not-found | partial
|
kind: post | list | not_found | partial
|
||||||
enabled: Boolean
|
enabled: Boolean
|
||||||
version: Integer
|
version: Integer
|
||||||
created_at: Timestamp
|
created_at: Timestamp
|
||||||
@@ -122,9 +122,8 @@ rule WriteTemplateFile {
|
|||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|
||||||
value ScriptFrontmatter {
|
value ScriptFrontmatter {
|
||||||
-- File path: scripts/{slug}.lua (Rust) / {slug}.py (TypeScript legacy)
|
-- File path: scripts/{slug}.lua
|
||||||
-- Note: TypeScript uses Python with triple-quote docstring delimiters
|
-- YAML frontmatter delimited by --- markers
|
||||||
-- Rust uses Lua with standard YAML frontmatter
|
|
||||||
id: String -- UUID v4
|
id: String -- UUID v4
|
||||||
slug: String
|
slug: String
|
||||||
title: String
|
title: String
|
||||||
@@ -261,9 +260,10 @@ invariant MenuOpmlFormat {
|
|||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|
||||||
invariant TimestampFormat {
|
invariant TimestampFormat {
|
||||||
-- All timestamps are Unix milliseconds (number in JavaScript)
|
-- Database: Unix milliseconds stored as INTEGER columns
|
||||||
-- Stored as ISO 8601 strings in YAML frontmatter
|
-- YAML frontmatter: ISO 8601 strings (e.g. 2024-03-15T14:30:00.000Z)
|
||||||
-- Example: 2024-03-15T14:30:00.000Z
|
-- Conversion on read: parse ISO 8601 → Unix ms
|
||||||
|
-- Conversion on write: Unix ms → ISO 8601
|
||||||
}
|
}
|
||||||
|
|
||||||
invariant YamlFormatting {
|
invariant YamlFormatting {
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ rule ToggleAssistantSidebar {
|
|||||||
|
|
||||||
value WindowTitleBar {
|
value WindowTitleBar {
|
||||||
-- Platform-adaptive
|
-- Platform-adaptive
|
||||||
-- menu_bar: rendered only on non-Mac platforms (5 groups: File, Edit, View, Blog, Help)
|
-- menu_bar: rendered only on non-Mac platforms (6 groups: App, File, Edit, View, Window, Help)
|
||||||
|
-- macOS: native menu bar via muda crate (same 6 groups)
|
||||||
-- Keyboard: Alt opens mnemonics, Alt+letter opens group
|
-- Keyboard: Alt opens mnemonics, Alt+letter opens group
|
||||||
-- Arrow keys navigate groups and items, Enter/Space activates
|
-- Arrow keys navigate groups and items, Enter/Space activates
|
||||||
-- View group hides devTools toggle when not in dev mode
|
-- View group hides devTools toggle when not in dev mode
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ entity Script {
|
|||||||
entrypoint: String -- Default: "render" for macros
|
entrypoint: String -- Default: "render" for macros
|
||||||
enabled: Boolean
|
enabled: Boolean
|
||||||
version: Integer -- Incremented on each update
|
version: Integer -- Incremented on each update
|
||||||
file_path: String -- scripts/{slug}.lua (Rust) / {slug}.py (TS)
|
file_path: String -- scripts/{slug}.lua
|
||||||
status: draft | published
|
status: draft | published
|
||||||
content: String? -- Draft body (null when published)
|
content: String? -- Draft body (null when published)
|
||||||
created_at: Timestamp
|
created_at: Timestamp
|
||||||
@@ -224,52 +224,19 @@ entity ChatMessage {
|
|||||||
created_at: Timestamp
|
created_at: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
entity AiProvider {
|
entity AiCachedModel {
|
||||||
id: String -- Provider key (e.g., "opencode", "mistral")
|
-- Cached model list fetched from endpoint /v1/models API.
|
||||||
name: String -- Display name
|
-- Keyed by endpoint kind so online and airplane caches are separate.
|
||||||
env: String? -- JSON array of env var names
|
endpoint_kind: String -- "online" | "airplane"
|
||||||
npm: String? -- Primary npm package
|
|
||||||
api: String? -- API base URL
|
|
||||||
doc: String? -- Documentation URL
|
|
||||||
updated_at: Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
entity AiModel {
|
|
||||||
provider: String -- FK → ai_providers.id
|
|
||||||
model_id: String
|
model_id: String
|
||||||
name: String -- Display name
|
name: String -- Display name
|
||||||
family: String? -- Model family (e.g., "claude-sonnet")
|
context_window: Integer?
|
||||||
attachment: Boolean -- Supports attachments
|
max_output_tokens: Integer?
|
||||||
reasoning: Boolean -- Supports reasoning
|
|
||||||
tool_call: Boolean -- Supports tool calls
|
|
||||||
structured_output: Boolean
|
|
||||||
temperature: Boolean -- Temperature control supported
|
|
||||||
knowledge: String? -- Knowledge cutoff date
|
|
||||||
release_date: String?
|
|
||||||
last_updated_date: String?
|
|
||||||
open_weights: Boolean
|
|
||||||
input_price: Integer? -- USD per 1M input tokens (in cents)
|
|
||||||
output_price: Integer? -- USD per 1M output tokens (in cents)
|
|
||||||
cache_read_price: Integer?
|
|
||||||
cache_write_price: Integer?
|
|
||||||
context_window: Integer -- Max context tokens
|
|
||||||
max_input_tokens: Integer
|
|
||||||
max_output_tokens: Integer
|
|
||||||
interleaved: String? -- JSON object for interleaved fields
|
|
||||||
status: String? -- e.g., "deprecated"
|
|
||||||
provider_npm: String? -- Per-model npm override
|
|
||||||
updated_at: Timestamp
|
updated_at: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
entity AiModelModality {
|
|
||||||
provider: String
|
|
||||||
model_id: String
|
|
||||||
direction: String -- "input" | "output"
|
|
||||||
modality: String -- "text" | "image" | "pdf" | "audio" | "video"
|
|
||||||
}
|
|
||||||
|
|
||||||
entity AiCatalogMeta {
|
entity AiCatalogMeta {
|
||||||
key: String -- "etag" | "lastFetchedAt"
|
key: String -- "{endpoint_kind}_etag" | "{endpoint_kind}_lastFetchedAt"
|
||||||
value: String
|
value: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
-- bDS Scripting System
|
-- bDS Scripting System
|
||||||
-- Scope: core (Wave 6 — Lua runtime and scripting docs)
|
-- Scope: core (Wave 6 — Lua runtime and scripting docs)
|
||||||
-- Distilled from: src/main/engine/ScriptEngine.ts, schema.ts
|
-- Distilled from: src/main/engine/ScriptEngine.ts, schema.ts
|
||||||
-- Note: TypeScript app uses Python/Pyodide. Rust rewrite uses Lua.
|
-- Scripting runtime is Lua (mlua crate). Behavioural contract preserved.
|
||||||
-- Behavioural contract is identical; only runtime changes.
|
|
||||||
|
|
||||||
entity Script {
|
entity Script {
|
||||||
slug: String
|
slug: String
|
||||||
@@ -37,8 +36,7 @@ invariant ScriptFileLayout {
|
|||||||
for s in Scripts where file_path != "":
|
for s in Scripts where file_path != "":
|
||||||
s.file_path = format("scripts/{slug}.lua", slug: s.slug)
|
s.file_path = format("scripts/{slug}.lua", slug: s.slug)
|
||||||
}
|
}
|
||||||
-- TypeScript app uses .py with triple-quote docstring frontmatter
|
-- Script files use .lua with standard --- YAML frontmatter
|
||||||
-- Rust app uses .lua with standard --- frontmatter
|
|
||||||
|
|
||||||
rule CreateScript {
|
rule CreateScript {
|
||||||
when: CreateScriptRequested(title, kind, content, entrypoint)
|
when: CreateScriptRequested(title, kind, content, entrypoint)
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ invariant TemplateLookupPriority {
|
|||||||
-- 4. Default "post" template
|
-- 4. Default "post" template
|
||||||
--
|
--
|
||||||
-- List pages use "list" template
|
-- List pages use "list" template
|
||||||
-- 404 pages use "not-found" template
|
-- 404 pages use "not_found" template
|
||||||
-- Partials are referenced via {% render 'partial' %}
|
-- Partials are referenced via {% render 'partial' %}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ rule RenderListPage {
|
|||||||
|
|
||||||
rule RenderNotFoundPage {
|
rule RenderNotFoundPage {
|
||||||
when: RenderNotFoundPageRequested()
|
when: RenderNotFoundPageRequested()
|
||||||
let template = first (t in Templates where t.kind = "not-found" and t.enabled)
|
let template = first (t in Templates where t.kind = "not_found" and t.enabled)
|
||||||
let context = BuildNotFoundContext
|
let context = BuildNotFoundContext
|
||||||
ensures: RenderedHtml = liquid_render(template.content, context)
|
ensures: RenderedHtml = liquid_render(template.content, context)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user