-- allium: 1 -- bDS Metadata Diff and Rebuild -- Scope: core (Wave 1) -- Distilled from: src/main/engine/MetadataDiffEngine.ts use "./post.allium" as post use "./media.allium" as media use "./script.allium" as script use "./template.allium" as template surface MetadataMaintenanceSurface { facing _: MaintenanceOperator provides: MetadataDiffRequested(project) RebuildFromFilesystemRequested(project, entity_type) RepairMetadataDiffItemRequested(project, direction, item) } value DiffField { name: String db_value: String file_value: String } value DiffReport { entity_type: String -- post, post_translation, media, -- media_translation, script, template, embedding entity_id: String differences: List } value OrphanReport { file_path: String -- File exists on disk but has no DB record } rule RunMetadataDiff { when: MetadataDiffRequested(project) -- Runs as background task via TaskManager -- Compares DB records against filesystem files for: -- posts, translations, media, scripts, templates -- Detected fields: tags, categories, title, excerpt, author, -- language, status, templateSlug, dates for post in project.posts: let file_data = parse_post_file(post.file_path) let diffs = compare_fields(post, file_data) if diffs.count > 0: ensures: DiffReport.created(entity_type: "post", entity_id: post.id, differences: diffs) -- Translation files only carry language-specific metadata. Shared status and -- timestamp fields come from the canonical post and must not be reported as -- missing when they are absent from the translation file. for translation in project.post_translations: let translation_file_data = parse_post_file(translation.file_path) let translation_diffs = compare_translation_specific_fields(translation, translation_file_data) if translation_diffs.count > 0: ensures: DiffReport.created( entity_type: "post_translation", entity_id: translation.id, differences: translation_diffs ) -- Detect orphan files (on disk but not in DB) for file in scan_directory(project.public_dir + "/posts", "*.md"): let matching = Posts where file_path = file if matching.count = 0: ensures: OrphanReport.created(file_path: file) -- Same pattern for media sidecars (media), media translation sidecars -- (media_translation), scripts, templates, and embeddings. -- Embedding diffs compare the stored content_hash against the live post -- content to detect vectors that need recomputation. } rule RepairMetadataDiffItem { when: RepairMetadataDiffItemRequested(project, direction, item) -- Resolves a single diff in one direction. -- direction = file_to_db (filesystem wins) | db_to_file (database wins) -- Dispatched per item.entity_type: -- project | categories | category_meta | publishing -> project metadata sync/flush -- post -> sync_post_from_file / rewrite_published_post -- post_translation -> sync_post_translation_from_file / rewrite_published_post_translation -- media -> sync_media_from_sidecar / sync_media_sidecar -- media_translation -> sync_media_translation_from_sidecar / sync_media_translation_sidecar -- script -> sync_script_from_file / sync_published_script_file -- template -> sync_template_from_file / sync_published_template_file -- embedding -> sync_post (file_to_db) / refresh_snapshot (db_to_file) -- Unknown entity_type or direction -> unsupported error. } rule RebuildFromFilesystem { when: RebuildFromFilesystemRequested(project, entity_type) -- The inverse of metadata diff: filesystem is treated as truth -- Reads all files and upserts into DB ensures: if entity_type = "post": post/RebuildPostsFromFiles(project) if entity_type = "media": media/RebuildMediaFromFiles(project) if entity_type = "script": script/RebuildScriptsFromFiles(project) if entity_type = "template": template/RebuildTemplatesFromFiles(project) } invariant ThreeWaySync { -- Metadata must stay in sync across three representations: -- 1. Database records -- 2. Filesystem files (frontmatter/sidecars) -- 3. Generated site output -- MetadataDiff detects divergence between (1) and (2) -- Rebuild resolves divergence by treating (2) as truth -- Site generation consumes (1) to produce (3) }