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