feat: alignment on media import event shape

This commit is contained in:
2026-05-01 18:49:28 +02:00
parent be439f929f
commit f39fe9c40d
4 changed files with 60 additions and 14 deletions

View File

@@ -54,7 +54,7 @@ Goal: align bDS2 with old bDS behavior. Use the Allium specs as the contract onl
- Spec: now specifies WebP quality 80 and AI JPEG quality 85.
- Action: tend `specs/media_processing.allium` to specify WebP quality 80 and AI JPEG quality 85.
## P2: Media Import Event Shape
## P2: Media Import Event Shape (done)
- Old bDS: imports by source path plus optional metadata in project context.
- bDS2 now: imports with attrs including `source_path` and `project_id`.

View File

@@ -9,7 +9,7 @@ surface MediaControlSurface {
facing _: MediaOperator
provides:
ImportMediaRequested(project, source_file)
ImportMediaRequested(source_path, project, metadata)
UpdateMediaRequested(media, changes)
DeleteMediaRequested(media)
UpsertMediaTranslationRequested(media, language, title, alt, caption)
@@ -121,22 +121,29 @@ invariant DateBasedMediaLayout {
}
rule ImportMedia {
when: ImportMediaRequested(project, source_file)
let uuid_name = generate_uuid() + extension(source_file)
when: ImportMediaRequested(source_path, project, metadata)
-- metadata is optional import context: title, alt, caption, author,
-- language, and tags may be supplied by the caller.
let uuid_name = generate_uuid() + extension(source_path)
let dest = format("media/{yyyy}/{mm}/{uuid_name}",
yyyy: now.year, mm: now.month_padded)
ensures: Media.created(
project: project,
filename: uuid_name,
original_name: source_file.name,
mime_type: detect_mime(source_file),
size: source_file.size,
width: detect_width(source_file),
height: detect_height(source_file),
original_name: basename(source_path),
mime_type: detect_mime(source_path),
size: file_size(source_path),
width: detect_width(source_path),
height: detect_height(source_path),
title: metadata.title,
alt: metadata.alt,
caption: metadata.caption,
author: metadata.author,
language: metadata.language,
file_path: dest,
tags: {}
tags: metadata.tags
)
ensures: FileCopied(source_file, dest)
ensures: FileCopied(source_path, dest)
ensures: SidecarWritten(media)
ensures: ThumbnailsGenerated(media)
ensures: SearchIndexUpdated(media)

View File

@@ -14,7 +14,7 @@ surface MediaProcessingControlSurface {
facing _: MediaProcessingOperator
provides:
ImportMediaRequested(source_path, project)
ImportMediaRequested(source_path, project, metadata)
TagMediaRequested(media, tags)
DeleteMediaRequested(media)
ValidateMediaRequested(project)
@@ -239,11 +239,13 @@ invariant MediaTranslationFileLayout {
-- ============================================================================
rule ImportMedia {
when: ImportMediaRequested(source_path, project)
when: ImportMediaRequested(source_path, project, metadata)
-- metadata is optional import context: title, alt, caption, author,
-- language, and tags may be supplied by the caller.
-- 1. Validate file type (must be supported image)
-- 2. Generate UUID v4 filename
-- 3. Copy to media/{YYYY}/{MM}/{uuid}.{ext}
-- 4. Write sidecar {binary_path}.meta
-- 4. Apply optional metadata and write sidecar {binary_path}.meta
-- 5. Generate four thumbnail sizes
-- 6. Index for search (FTS5)
ensures: media/Media.created(
@@ -253,6 +255,12 @@ rule ImportMedia {
size: file_size(source_path),
width: extract_width_from_header(source_path),
height: extract_height_from_header(source_path),
title: metadata.title,
alt: metadata.alt,
caption: metadata.caption,
author: metadata.author,
language: metadata.language,
tags: metadata.tags,
file_path: format("media/{yyyy}/{mm}/{uuid}.{ext}"),
sidecar_path: format("media/{yyyy}/{mm}/{uuid}.{ext}.meta"),
checksum: sha256(source_path)

View File

@@ -0,0 +1,31 @@
defmodule BDS.AlignmentTest do
use ExUnit.Case, async: true
@media_import_specs [
"specs/media.allium",
"specs/media_processing.allium"
]
test "media import specs use one source-path-first event shape" do
signatures =
@media_import_specs
|> Enum.flat_map(fn path ->
path
|> File.read!()
|> import_media_signatures(path)
end)
assert signatures == [
{"specs/media.allium", "source_path, project, metadata"},
{"specs/media.allium", "source_path, project, metadata"},
{"specs/media_processing.allium", "source_path, project, metadata"},
{"specs/media_processing.allium", "source_path, project, metadata"}
]
end
defp import_media_signatures(source, path) do
~r/ImportMediaRequested\(([^)]+)\)/
|> Regex.scan(source, capture: :all_but_first)
|> Enum.map(fn [signature] -> {path, signature} end)
end
end