96 lines
3.3 KiB
Plaintext
96 lines
3.3 KiB
Plaintext
-- 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)
|
|
}
|
|
|
|
value DiffField {
|
|
name: String
|
|
db_value: String
|
|
file_value: String
|
|
}
|
|
|
|
value DiffReport {
|
|
entity_type: String -- post, media, script, template
|
|
entity_id: String
|
|
differences: List<DiffField>
|
|
}
|
|
|
|
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.effective_data_dir + "/posts", "*.md"):
|
|
let matching = Posts where file_path = file
|
|
if matching.count = 0:
|
|
ensures: OrphanReport.created(file_path: file)
|
|
|
|
-- Same pattern for media sidecar files, scripts, templates
|
|
}
|
|
|
|
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)
|
|
}
|