chore: reworked some specs and did small addition to database schema based on gaps in spec, also rechecked the core plan

This commit is contained in:
2026-07-18 22:21:02 +02:00
parent 943e5fa39b
commit 5c1b333cce
12 changed files with 183 additions and 54 deletions

View File

@@ -32,7 +32,7 @@ Status in this document describes the current source code as of 2026-07-18. It d
## Current Core Status ## Current Core Status
### Project, persistence, and integrity — Mostly done ### Project, persistence, and integrity — Done
Available: Available:
@@ -45,11 +45,9 @@ Available:
- Database rebuild from filesystem and directional metadata diff/repair. - Database rebuild from filesystem and directional metadata diff/repair.
- Structured site, media, and translation validation. - Structured site, media, and translation validation.
- Unbounded unique slug allocation. - Unbounded unique slug allocation.
- Explicit rebuild-from-filesystem paths for manual file changes. bDS2 does
Open: not live-watch arbitrary project files; its external-change watcher is the
extension CLI/database-notification contract.
- Watch project files for external changes and reconcile open editors, preview, and database state.
- Make the script file extension project-configurable instead of hard-coding `.lua`.
### Native desktop shell — Done ### Native desktop shell — Done
@@ -75,11 +73,11 @@ Available:
- Template and Lua script creation, editing, validation, publication, and deletion. - Template and Lua script creation, editing, validation, publication, and deletion.
- Rope-based editing with syntax highlighting, selection, clipboard, undo/redo, word/line/page movement, line numbers, soft wrapping, mouse selection, and committed IME input. - Rope-based editing with syntax highlighting, selection, clipboard, undo/redo, word/line/page movement, line numbers, soft wrapping, mouse selection, and committed IME input.
- Automatic translation flows with airplane-mode gating and media translation propagation. - Automatic translation flows with airplane-mode gating and media translation propagation.
- Functional post-links panel with backlinks, outlinks, and navigation to linked posts.
Open: Open:
- Replace the post-links placeholder with the specified functional linked-post view. - Add the specified post-editor "Add Gallery Images" batch workflow.
- Add the specified batch workflow for images linked from post content.
### Rendering, preview, and generation — Mostly done ### Rendering, preview, and generation — Mostly done
@@ -107,26 +105,26 @@ Available:
- Model catalog discovery and model selection. - Model catalog discovery and model selection.
- Post translation, media translation, image alt text, post analysis, taxonomy analysis, and language detection. - Post translation, media translation, image alt text, post analysis, taxonomy analysis, and language detection.
- Explicit offline gating and user-visible errors. - Explicit offline gating and user-visible errors.
- SQLite fields for input, output, cache-read, and cache-write token usage.
Open: Open:
- Persist actual input, output, cache-read, and cache-write token usage where the schema provides those fields. - Parse actual input, output, cache-read, and cache-write token usage from AI
responses and return it with each one-shot result. Chat persistence of those
counters belongs to the extension chat workflow.
Interactive chat, tools, agents, and MCP belong to the extension plan. Interactive chat, tools, agents, and MCP belong to the extension plan.
### Publishing — Mostly done ### Publishing — Done
Available: Available:
- SCP and rsync publishing through system commands. - SCP and rsync publishing through system commands.
- SSH-agent-only authentication with no password prompts. - SSH-agent-only authentication with no password prompts.
- Separate HTML, thumbnail, and media targets. - Separate HTML, thumbnail, and media targets.
- `.meta` exclusion, changed-file skipping for SCP, progress reporting, cancellation, and UI commands. - `.meta` exclusion, changed-file skipping for SCP, progress reporting, and UI commands.
- One managed publish job processes the HTML, thumbnail, and media targets in
Open: sequence, matching bDS2. Parallel target uploads are not a parity requirement.
- Run the three upload targets as parallel tasks instead of processing them sequentially.
- Integrate external-file watching with publish/integrity workflows.
### Lua scripting — Partly done ### Lua scripting — Partly done
@@ -136,20 +134,19 @@ Available:
- Application log, progress, and toast functions. - Application log, progress, and toast functions.
- User macro and transform execution, including Blogmark transforms. - User macro and transform execution, including Blogmark transforms.
- Lua script persistence, rebuild, metadata diff, validation, and editor support. - Lua script persistence, rebuild, metadata diff, validation, and editor support.
- Fixed `.lua` script file contract.
Open: Open:
- Expose the specified `bds.*` API for post, media, tag, project, and other script-visible data. - Expose the bDS2-compatible core `bds.*` API for post, media, tag, project, and other script-visible data.
- Generate and bundle the Lua API reference, canonical type reference, and macro/transform/utility examples under `docs/scripting/`. - Generate and bundle the Lua API reference, canonical type reference, and macro/transform/utility examples under `docs/scripting/`.
- Add a documentation-sync check tied to the exposed API. - Add a documentation-sync check tied to the exposed API.
## Remaining Core Blocks ## Remaining Core Blocks
1. Complete the Lua host API and scripting documentation. 1. Complete the Lua host API and scripting documentation.
2. Add filesystem change watching and reconciliation. 2. Finish the linked-image authoring workflow.
3. Parallelize publishing targets. 3. Add generation section-task grouping.
4. Finish the linked-post and linked-image authoring workflows. 4. Return normalized token accounting from one-shot AI calls.
5. Add generation section-task grouping and AI token accounting.
6. Remove the hard-coded Lua script extension.
Core is feature-complete when these blocks are closed and the implementation continues to satisfy the Allium and bDS2 compatibility contracts. Core is feature-complete when these blocks are closed and the implementation continues to satisfy the Allium and bDS2 compatibility contracts.

View File

@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE chat_messages DROP COLUMN token_usage_output;
ALTER TABLE chat_messages DROP COLUMN token_usage_input;

View File

@@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE chat_messages ADD COLUMN token_usage_input INTEGER;
ALTER TABLE chat_messages ADD COLUMN token_usage_output INTEGER;

View File

@@ -35,7 +35,7 @@ mod tests {
let applied = db let applied = db
.conn() .conn()
.with_migrations(|conn| conn.applied_migrations().unwrap().len()); .with_migrations(|conn| conn.applied_migrations().unwrap().len());
assert_eq!(applied, 1); assert_eq!(applied, 2);
} }
#[test] #[test]
@@ -82,7 +82,7 @@ mod tests {
#[test] #[test]
fn migrations_expose_current_ai_catalog_and_usage_columns() { fn migrations_expose_current_ai_catalog_and_usage_columns() {
let db = migrated_database(); let db = migrated_database();
let (provider_ref, model_ref, cache_tokens) = db let (provider_ref, model_ref, usage_tokens) = db
.conn() .conn()
.with(|conn| { .with(|conn| {
diesel::insert_into(ai_providers::table) diesel::insert_into(ai_providers::table)
@@ -124,6 +124,8 @@ mod tests {
chat_messages::conversation_id.eq("conversation"), chat_messages::conversation_id.eq("conversation"),
chat_messages::role.eq("assistant"), chat_messages::role.eq("assistant"),
chat_messages::created_at.eq(1_i64), chat_messages::created_at.eq(1_i64),
chat_messages::token_usage_input.eq(Some(56)),
chat_messages::token_usage_output.eq(Some(78)),
chat_messages::cache_read_tokens.eq(Some(12)), chat_messages::cache_read_tokens.eq(Some(12)),
chat_messages::cache_write_tokens.eq(Some(34)), chat_messages::cache_write_tokens.eq(Some(34)),
)) ))
@@ -138,16 +140,18 @@ mod tests {
.first::<Option<String>>(conn)?, .first::<Option<String>>(conn)?,
chat_messages::table chat_messages::table
.select(( .select((
chat_messages::token_usage_input,
chat_messages::token_usage_output,
chat_messages::cache_read_tokens, chat_messages::cache_read_tokens,
chat_messages::cache_write_tokens, chat_messages::cache_write_tokens,
)) ))
.first::<(Option<i32>, Option<i32>)>(conn)?, .first::<(Option<i32>, Option<i32>, Option<i32>, Option<i32>)>(conn)?,
)) ))
}) })
.unwrap(); .unwrap();
assert_eq!(provider_ref.as_deref(), Some("provider-package")); assert_eq!(provider_ref.as_deref(), Some("provider-package"));
assert_eq!(model_ref.as_deref(), Some("model-package")); assert_eq!(model_ref.as_deref(), Some("model-package"));
assert_eq!(cache_tokens, (Some(12), Some(34))); assert_eq!(usage_tokens, (Some(56), Some(78), Some(12), Some(34)));
} }
} }

View File

@@ -80,6 +80,8 @@ diesel::table! {
created_at -> BigInt, created_at -> BigInt,
cache_read_tokens -> Nullable<Integer>, cache_read_tokens -> Nullable<Integer>,
cache_write_tokens -> Nullable<Integer>, cache_write_tokens -> Nullable<Integer>,
token_usage_input -> Nullable<Integer>,
token_usage_output -> Nullable<Integer>,
} }
} }

View File

@@ -240,8 +240,7 @@ pub fn view<'a>(
vec![ vec![
button(text(t(locale, "editor.quickActions")).size(13)) button(text(t(locale, "editor.quickActions")).size(13))
.on_press_maybe( .on_press_maybe(
ai_enabled ai_enabled.then_some(Message::MediaEditor(MediaEditorMsg::ToggleQuickActions)),
.then_some(Message::MediaEditor(MediaEditorMsg::ToggleQuickActions)),
) )
.style(inputs::secondary_button) .style(inputs::secondary_button)
.padding([6, 16]) .padding([6, 16])
@@ -549,7 +548,11 @@ pub fn view<'a>(
.into() .into()
} }
fn quick_action_item<'a>(label: String, msg: MediaEditorMsg, enabled: bool) -> Element<'a, Message> { fn quick_action_item<'a>(
label: String,
msg: MediaEditorMsg,
enabled: bool,
) -> Element<'a, Message> {
button(text(label).size(12).shaping(Shaping::Advanced)) button(text(label).size(12).shaping(Shaping::Advanced))
.on_press_maybe(enabled.then_some(Message::MediaEditor(msg))) .on_press_maybe(enabled.then_some(Message::MediaEditor(msg)))
.padding([6, 12]) .padding([6, 12])
@@ -612,7 +615,8 @@ mod tests {
#[test] #[test]
fn quick_actions_menu_starts_closed() { fn quick_actions_menu_starts_closed() {
let state = MediaEditorState::from_media(&make_media(), &["en".to_string()], &[], Vec::new()); let state =
MediaEditorState::from_media(&make_media(), &["en".to_string()], &[], Vec::new());
assert!(!state.quick_actions_open); assert!(!state.quick_actions_open);
} }

View File

@@ -192,6 +192,17 @@ surface OneShotAiSurface {
DetectLanguageRequested(text) DetectLanguageRequested(text)
TranslatePostRequested(post, target_language) TranslatePostRequested(post, target_language)
TranslateMediaRequested(media, target_language) TranslateMediaRequested(media, target_language)
@guarantee UsageReturnedWithSuccessfulResult
-- Every successful one-shot result includes a usage value with nullable
-- input_tokens, output_tokens, cache_read_tokens, and
-- cache_write_tokens. Missing provider counters remain null. One-shot
-- calls return this accounting to their caller; they do not create a
-- chat conversation or persist a ChatMessage.
-- OpenAI-compatible responses normalize prompt_tokens to input_tokens,
-- completion_tokens to output_tokens, prompt_tokens_details.cached_tokens
-- to cache_read_tokens, and completion_tokens_details.cached_tokens to
-- cache_write_tokens.
} }
surface AiChatSurface { surface AiChatSurface {
@@ -236,7 +247,7 @@ rule AnalyzeTaxonomy {
when: AnalyzeTaxonomyRequested(post) when: AnalyzeTaxonomyRequested(post)
requires: active_endpoint_configured requires: active_endpoint_configured
-- Suggests tags and categories for a post -- Suggests tags and categories for a post
ensures: TaxonomySuggestion(tags, categories) ensures: TaxonomySuggestion(tags, categories, usage)
} }
rule AnalyzeImage { rule AnalyzeImage {
@@ -244,34 +255,34 @@ rule AnalyzeImage {
requires: active_endpoint_configured requires: active_endpoint_configured
requires: is_image(media.mime_type) requires: is_image(media.mime_type)
-- Vision model generates alt text and caption -- Vision model generates alt text and caption
ensures: ImageAnalysisResult(alt, caption) ensures: ImageAnalysisResult(title, alt, caption, usage)
} }
rule AnalyzePost { rule AnalyzePost {
when: AnalyzePostRequested(post) when: AnalyzePostRequested(post)
requires: active_endpoint_configured requires: active_endpoint_configured
-- Generates title, excerpt, slug suggestions -- Generates title, excerpt, slug suggestions
ensures: PostAnalysisResult(title, excerpt, slug) ensures: PostAnalysisResult(title, excerpt, slug, usage)
} }
rule DetectLanguage { rule DetectLanguage {
when: DetectLanguageRequested(text) when: DetectLanguageRequested(text)
requires: active_endpoint_configured requires: active_endpoint_configured
ensures: LanguageDetectionResult(language_code) ensures: LanguageDetectionResult(language_code, usage)
} }
rule TranslatePost { rule TranslatePost {
when: TranslatePostRequested(post, target_language) when: TranslatePostRequested(post, target_language)
requires: active_endpoint_configured 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, usage)
} }
rule TranslateMedia { rule TranslateMedia {
when: TranslateMediaRequested(media, target_language) when: TranslateMediaRequested(media, target_language)
requires: active_endpoint_configured 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, usage)
} }
-- Chat (extension Bucket C scope, with streaming and tool use) -- Chat (extension Bucket C scope, with streaming and tool use)
@@ -303,6 +314,10 @@ rule SendChatMessage {
-- Blog data tools for post/media querying and mutation during chat. -- Blog data tools for post/media querying and mutation during chat.
-- Render tools may emit structured chart/table/form payloads. -- Render tools may emit structured chart/table/form payloads.
-- Token usage tracking includes input, output, cache read, cache write. -- Token usage tracking includes input, output, cache read, cache write.
ensures: AssistantChatMessagesPersistNormalizedUsage(conversation)
-- Each persisted assistant response stores its normalized input,
-- output, cache-read, and cache-write counters. User and tool messages
-- leave those nullable fields empty.
} }
rule CancelChat { rule CancelChat {

View File

@@ -78,10 +78,6 @@ surface MenuOpmlSurface {
item.slug item.slug
} }
config {
script_extension: String = "lua"
}
-- ============================================================================ -- ============================================================================
-- POST FILE FORMAT -- POST FILE FORMAT
-- ============================================================================ -- ============================================================================
@@ -239,7 +235,7 @@ rule WriteTemplateFile {
-- ============================================================================ -- ============================================================================
value ScriptFrontmatter { value ScriptFrontmatter {
-- File path: scripts/{slug}.{extension} -- File path: scripts/{slug}.lua
-- YAML frontmatter delimited by --- markers -- YAML frontmatter delimited by --- markers
-- All keys serialized as camelCase in YAML frontmatter -- All keys serialized as camelCase in YAML frontmatter
id: String -- UUID v4 id: String -- UUID v4
@@ -258,7 +254,7 @@ rule WriteScriptFile {
when: PublishScriptRequested(script) when: PublishScriptRequested(script)
requires: ValidateScript(script.content) = valid requires: ValidateScript(script.content) = valid
ensures: FileWritten( ensures: FileWritten(
path: format("scripts/{slug}.{extension}", slug: script.slug, extension: config.script_extension), path: format("scripts/{slug}.lua", slug: script.slug),
content: format_script_file(script) content: format_script_file(script)
) )
ensures: script.content = null ensures: script.content = null

View File

@@ -17,6 +17,24 @@ surface MediaControlSurface {
RebuildMediaFromFilesRequested(project) RebuildMediaFromFilesRequested(project)
} }
surface PostEditorGalleryImportSurface {
facing _: MediaOperator
provides:
BatchImportImagesRequested(project, post, file_paths, language)
@guarantee NativeImageSelection
-- The post-editor "Add Gallery Images" quick action opens the native
-- multi-file picker restricted to images. Cancelling the picker is a
-- no-op; picker failures are reported in the Output panel.
@guarantee AirplaneModeGate
-- Online mode uses the configured online endpoint. Airplane mode may
-- proceed only when its local endpoint is configured. If it is not,
-- no picker opens and the Output panel explains that automatic AI
-- actions remain gated by airplane mode.
}
value ThumbnailSet { value ThumbnailSet {
small: String -- 150px width (binary path) small: String -- 150px width (binary path)
medium: String -- 400px width (binary path) medium: String -- 400px width (binary path)
@@ -229,20 +247,33 @@ invariant SidecarRoundtrip {
rule BatchImportProcessLinkImages { rule BatchImportProcessLinkImages {
when: BatchImportImagesRequested(project, post, file_paths, language) when: BatchImportImagesRequested(project, post, file_paths, language)
requires: not OfflineMode
for source_path in file_paths where is_image(source_path): for source_path in file_paths where is_image(source_path):
let media = ImportMedia(source_path, project) let media = ImportMedia(source_path, project)
let analysis = AnalyzeImage(media, language)
ensures: MediaUpdated(media, analysis)
ensures: PostMediaLinked(media, post) ensures: PostMediaLinked(media, post)
-- Import and linking are mandatory and happen before enrichment.
if ActiveAiEndpointConfigured:
let analysis = AnalyzeImage(media, language)
ensures: MediaUpdated(media, analysis)
for target_language in unique(
{project.main_language} + project.blog_languages - {language}
):
ensures: MediaTranslationUpserted(media, target_language)
if not ActiveAiEndpointConfigured or AiEnrichmentFailed(media):
ensures: PostMediaLinked(media, post)
-- AI analysis, metadata update, and translation are
-- best-effort and never undo a successful import/link.
@guidance @guidance
-- Triggered from post editor quick action "Add Gallery Images". -- Triggered from post editor quick action "Add Gallery Images".
-- AI results auto-applied without user confirmation. -- AI results auto-applied without user confirmation.
-- After metadata is set, media is auto-translated to all configured blog languages. -- After metadata is set, media is auto-translated to the project main
-- Non-image files skipped entirely. -- language and all configured blog languages except the source
-- language, with duplicate targets removed.
-- Non-image files are not linked or enriched.
-- Concurrency limit from project metadata image_import_concurrency (default 4, min 1, max 8). -- Concurrency limit from project metadata image_import_concurrency (default 4, min 1, max 8).
-- Toast per completed image + final summary toast. -- Each successful image and each failed image emits its own Output
-- On completion: [[gallery]] macro inserted into post content and post editor refreshed. -- panel entry. The final entry reports the selected image count.
-- After every worker has finished, including partial failures,
-- \n[[gallery]]\n is inserted into post content and the editor refreshes.
} }
config { config {

View File

@@ -65,6 +65,12 @@ surface PublishingRuntimeSurface {
provides: provides:
PublishJobStarted(project, job, credentials) PublishJobStarted(project, job, credentials)
PublishTargetFailed(job, target, error) PublishTargetFailed(job, target, error)
@guarantee SequentialTargetProcessing
-- One managed publish job processes html, thumbnails, then media in
-- that order, matching bDS2. It reports progress at each target and
-- stops on the first failed target. Parallel target upload is not part
-- of the compatibility contract.
} }
rule UploadSite { rule UploadSite {

View File

@@ -154,7 +154,7 @@ entity Script {
entrypoint: String -- Default: "render" for macros, "main" otherwise entrypoint: String -- Default: "render" for macros, "main" otherwise
enabled: Boolean enabled: Boolean
version: Integer -- Incremented on each update version: Integer -- Incremented on each update
file_path: String -- scripts/{slug}.{extension} file_path: String -- scripts/{slug}.lua
status: ScriptStatus status: ScriptStatus
content: String? -- Draft body (null when published) content: String? -- Draft body (null when published)
created_at: Timestamp created_at: Timestamp
@@ -239,6 +239,8 @@ entity ChatMessage {
content: String? content: String?
tool_call_id: String? -- For tool responses tool_call_id: String? -- For tool responses
tool_calls: String? -- JSON array of tool calls tool_calls: String? -- JSON array of tool calls
token_usage_input: Integer?
token_usage_output: Integer?
cache_read_tokens: Integer? cache_read_tokens: Integer?
cache_write_tokens: Integer? cache_write_tokens: Integer?
created_at: Timestamp created_at: Timestamp
@@ -524,6 +526,8 @@ surface ChatMessageRecordSurface {
message.content when message.content != null message.content when message.content != null
message.tool_call_id when message.tool_call_id != null message.tool_call_id when message.tool_call_id != null
message.tool_calls when message.tool_calls != null message.tool_calls when message.tool_calls != null
message.token_usage_input when message.token_usage_input != null
message.token_usage_output when message.token_usage_output != null
message.cache_read_tokens when message.cache_read_tokens != null message.cache_read_tokens when message.cache_read_tokens != null
message.cache_write_tokens when message.cache_write_tokens != null message.cache_write_tokens when message.cache_write_tokens != null
message.created_at message.created_at

View File

@@ -7,7 +7,6 @@
-- only the behavioural contract is normative here. -- only the behavioural contract is normative here.
config { config {
script_extension: String = "lua"
macro_timeout: Duration = 10.seconds macro_timeout: Duration = 10.seconds
transform_max_toasts_per_script: Integer = 5 transform_max_toasts_per_script: Integer = 5
transform_max_toasts_total: Integer = 20 transform_max_toasts_total: Integer = 20
@@ -94,6 +93,71 @@ surface ScriptRuntimeSurface {
-- Host-provided functions are exposed only through an explicit bds.* -- Host-provided functions are exposed only through an explicit bds.*
-- capability table, never through ambient global access. -- capability table, never through ambient global access.
@guarantee CoreHostApiCompatibility
-- The core API matches the bDS2 capability names below. Methods are
-- bound to the active project unless an explicit project id is part of
-- the method. Arguments and return tables use the canonical types in
-- the generated scripting reference.
--
-- bds.app: copy_to_clipboard, get_data_paths,
-- get_blogmark_bookmarklet, get_system_language,
-- get_default_project_path, get_title_bar_metrics, log,
-- notify_renderer_ready, open_folder, read_project_metadata,
-- select_folder, set_preview_post_target, show_item_in_folder,
-- trigger_menu_action.
-- bds.projects: create, delete, delete_with_data, get, get_all,
-- get_active, set_active, update.
-- bds.meta: get_project_metadata, update_project_metadata,
-- set_project_metadata, add_category, remove_category, add_tag,
-- remove_tag, get_categories, get_tags,
-- get_publishing_preferences, set_publishing_preferences,
-- clear_publishing_preferences, sync_on_startup.
-- bds.posts: create, discard, filter, generate_unique_slug,
-- get_by_status, get_by_year_month, get_dashboard_stats,
-- get_linked_by, get_links_to, get_preview_url, update, delete, get,
-- get_all, get_by_slug, get_categories, get_categories_with_counts,
-- get_tags, get_tags_with_counts, get_translation, get_translations,
-- has_published_version, is_slug_available, publish,
-- publish_translation, rebuild_from_files, rebuild_links,
-- reindex_text, search.
-- bds.media: delete_translation, filter, import, get_by_year_month,
-- get_file_path, update, delete, get, get_all, get_tags,
-- get_tags_with_counts, get_thumbnail, get_translation,
-- get_translations, get_url, rebuild_from_files,
-- regenerate_missing_thumbnails, regenerate_thumbnails, reindex_text,
-- replace_file, search, upsert_translation.
-- bds.scripts: create, update, delete, get, get_all, publish,
-- rebuild_from_files.
-- bds.templates: create, update, delete, get, get_all, publish,
-- get_enabled_by_kind, rebuild_from_files, validate.
-- bds.tags: create, update, delete, get, get_all, get_by_name,
-- get_posts_with_tag, get_with_counts, merge, rename,
-- sync_from_posts.
-- bds.tasks: get, status_snapshot, cancel, get_all, get_running,
-- clear_completed.
-- bds.publish: upload_site.
-- bds.chat: detect_post_language, analyze_post, translate_post,
-- analyze_media_image, detect_media_language,
-- translate_media_metadata. Despite its historical namespace, this is
-- the core one-shot AI bridge; conversational chat remains extension.
-- bds.report_progress(payload) reports managed-job progress. The Rust
-- host may retain bds.app.progress and bds.app.toast as additive APIs.
-- bds.sync and bds.embeddings belong to the extension contract.
@guarantee HostApiFailureValues
-- Host failures do not expose implementation exceptions across the Lua
-- boundary. Lookup/create/update methods return nil on failure and
-- boolean command methods return false, as declared by the generated
-- method signature. Returned records contain only serializable public
-- fields; timestamps are ISO-8601 strings and enum atoms are strings.
@guarantee GeneratedApiReferenceStaysInSync
-- One machine-readable method/type manifest generates the bundled Lua
-- API reference and editor completion data. A verification test fails
-- when the exposed capability table, manifest, or generated reference
-- diverge. Canonical macro, transform, and utility examples are bundled
-- under docs/scripting/ and execute against the same host API.
@guarantee MacroTimeout @guarantee MacroTimeout
-- Macro execution has a short timeout budget of config.macro_timeout. -- Macro execution has a short timeout budget of config.macro_timeout.
@@ -130,7 +194,7 @@ invariant UniqueScriptSlug {
invariant ScriptFileLayout { invariant ScriptFileLayout {
for s in Scripts where file_path != "": for s in Scripts where file_path != "":
s.file_path = format("scripts/{slug}.{extension}", slug: s.slug, extension: config.script_extension) s.file_path = format("scripts/{slug}.lua", slug: s.slug)
} }
-- Script files use standard --- YAML frontmatter -- Script files use standard --- YAML frontmatter
@@ -185,7 +249,7 @@ rule CreateAndPublishScript {
status: published, status: published,
enabled: true, enabled: true,
version: 1, version: 1,
file_path: format("scripts/{slug}.{extension}", slug: slug, extension: config.script_extension) file_path: format("scripts/{slug}.lua", slug: slug)
) )
ScriptFileWritten(new_script) ScriptFileWritten(new_script)
} }
@@ -311,7 +375,7 @@ rule ExecuteTransform {
rule RebuildScriptsFromFiles { rule RebuildScriptsFromFiles {
when: RebuildScriptsFromFilesRequested(project) when: RebuildScriptsFromFilesRequested(project)
for file in scan_directory(project.public_dir + "/scripts", "*." + config.script_extension): for file in scan_directory(project.public_dir + "/scripts", "*.lua"):
let parsed = parse_script_file(file) let parsed = parse_script_file(file)
ensures: Script.created(parsed) ensures: Script.created(parsed)
} }