reslock

Deterministic resource lockfile and integrity manifest toolkit for MoonBit asset pipelines.

moonbit
assets
fingerprint
manifest
wasm
resource-pipeline
moon add WSH-ai-nb/reslock@0.1.0
Download zip
Author
Version
0.1.0
License
MIT
Last updated
18 hours ago
Downloads
3
README

#reslock

reslock 是一个 MoonBit 原生资源锁文件与完整性清单库,用于给 WebAssembly 应用、小游戏、静态站点和资源管线生成可复现的资源清单。它把资源路径、类型、字节长度、内容指纹、缓存策略和标签导出为稳定的 .reslock 文本格式,并提供解析、校验、摘要和 diff 能力。

#解决的问题

资源文件在构建、部署和缓存过程中经常发生隐式变化。reslock 让项目可以在发布前固定资源状态,检查路径是否安全、清单是否可复现、缓存策略是否合理,并在两个版本之间生成新增、删除和变更报告。

#适用场景

  • MoonBit Wasm 前端和小游戏资源清单;
  • 静态资源、教学项目、示例工程的发布前校验;
  • CI 中检查资源路径、重复条目和指纹变更;
  • 需要稳定文本格式的资源管线、缓存清单或审计记录。

#安装

发布到 Mooncakes 后可使用:

moon add WSH-ai-nb/reslock

本地开发时在项目中引用:

import {
"WSH-ai-nb/reslock" @reslock
}

#最小示例

let style = @reslock.asset(
"public\\css\\app.css",
"body{color:#24313f}",
kind=@reslock.Text,
tags=["css", "critical"],
).unwrap()

let manifest = @reslock.manifest("site-assets", "1.0.0", [style])
let lockfile = @reslock.render_manifest(manifest)
let parsed = @reslock.parse_manifest(lockfile).unwrap()
let summary = @reslock.summarize(parsed)
println(summary.manifest_fingerprint)

#本地运行

moon check moon build moon test moon run cmd/main

示例入口会生成内置资源清单、解析回读、输出 seal 摘要,并执行一次 no-change diff。

#核心功能

  • asset:从路径和内容构造资源条目,自动规范化路径、计算 FNV-1a 64 位指纹、规范化标签;
  • sealed_asset:从已有字节长度和指纹恢复条目,适合导入外部管线结果;
  • normalize_path:拒绝绝对路径、父目录穿越和不可导出的路径片段;
  • path_info:拆解资源路径的目录、文件名、扩展名、深度和隐藏文件状态;
  • render_manifest / parse_manifest:稳定导出和解析 .reslock 文本格式;
  • validate_manifest / is_valid:检查空清单、重复路径、非法指纹、非法标签、缓存策略风险;
  • diff_manifest / render_diff:生成新增、删除、变更的资源差异报告;
  • catalog_manifest:按目录、扩展名、资源类型、缓存策略、标签和资产桶生成统计目录;
  • assess_quality:输出质量分、风险等级、缓存策略建议和可读 findings;
  • evaluate_release_gate:按照发布门禁判断当前清单可直接发布、带警告发布或应阻断;
  • summarize_release_delta:把两个清单之间的 diff 归纳为 patch / minor / major 影响;
  • summarize:输出条目数、总字节数、清单指纹、错误数和警告数。

#支持范围

  • 文本内容的资源条目构造;
  • 路径规范化和安全路径校验;
  • 稳定 .reslock v1 文本格式;
  • FNV-1a 64 位非加密内容指纹;
  • 资源清单校验、摘要、diff 和 CLI smoke 示例;
  • 路径画像、资产分类、目录统计、质量评分和发布门禁;
  • 纯 MoonBit 实现,仅依赖 MoonBit core。

#暂不支持范围

  • 直接扫描文件系统;
  • 加密级哈希或签名;
  • 压缩、打包、上传和 CDN 发布;
  • 二进制文件读取;
  • Mooncakes 登录与正式发布自动化。

#测试与验收

moon check moon build moon test moon run cmd/main moon publish --dry-run

moon publish --dry-run 需要先确认 moon.mod 中的 Mooncakes owner 与当前登录账号一致,并完成 Mooncakes 登录。

#Mooncakes 包名

WSH-ai-nb/reslock

#许可证与参考说明

本项目采用 MIT 许可证。核心实现为原创 MoonBit 代码,不移植第三方源码,不包含来源不明素材;哈希算法使用公开的 FNV-1a 思路重新实现,仅作为非加密完整性指纹。

#
Asset

pub(all) struct Asset {
path : String
kind : AssetKind
bytes : Int
fingerprint : String
policy : CachePolicy
tags : Array[String]
note : String?
} derive(Eq)

One resource entry inside a deterministic resource lockfile.

#
AssetBucket

pub(all) enum AssetBucket {
BucketSource
BucketDocument
BucketConfig
BucketMedia
BucketArchive
BucketGenerated
BucketRuntime
BucketOther
} derive(Eq)

#
AssetBucket::to_wire

fn AssetBucket::to_wire(self : AssetBucket) -> String

#
AssetCatalog

pub(all) struct AssetCatalog {
manifest_name : String
manifest_version : String
entries : Int
total_bytes : Int
directories : Array[CountRow]
extensions : Array[CountRow]
kinds : Array[CountRow]
policies : Array[CountRow]
tags : Array[CountRow]
buckets : Array[CountRow]
} derive(Eq)

#
AssetKind

pub(all) enum AssetKind {
Text
Binary
Image
Audio
Font
Data
Other(String)
} derive(Eq)

Resource category recorded in a .reslock file.

#
AssetKind::to_wire

fn AssetKind::to_wire(self : AssetKind) -> String

#
CachePlan

pub(all) struct CachePlan {
path : String
current : CachePolicy
recommended : CachePolicy
tier : CacheTier
aligned : Bool
reason : String
} derive(Eq)

#
CachePolicy

pub(all) enum CachePolicy {
Immutable
Revalidate
NoStore
Runtime
} derive(Eq)

Cache and deployment intent for an asset.

#
CachePolicy::to_wire

fn CachePolicy::to_wire(self : CachePolicy) -> String

#
CacheTier

pub(all) enum CacheTier {
CacheStable
CacheRefreshable
CacheTransient
CacheCallerManaged
} derive(Eq)

#
CacheTier::to_wire

fn CacheTier::to_wire(self : CacheTier) -> String

#
CountRow

pub(all) struct CountRow {
key : String
count : Int
bytes : Int
} derive(Eq)

#
DiffKind

pub(all) enum DiffKind {
Added
Removed
Changed
} derive(Eq)

Diff category for comparing two manifests.

#
DirectoryProfile

pub(all) struct DirectoryProfile {
directory : String
entries : Int
bytes : Int
generated_entries : Int
runtime_entries : Int
media_entries : Int
source_entries : Int
document_entries : Int
warnings : Int
} derive(Eq)

#
EntryDiff

pub(all) struct EntryDiff {
kind : DiffKind
path : String
before : Asset?
after : Asset?
reasons : Array[String]
} derive(Eq)

A single manifest diff row.

#
Finding

pub(all) struct Finding {
level : FindingLevel
code : String
path : String?
message : String
} derive(Eq)

A validation finding with a stable machine-readable code.

#
FindingLevel

pub(all) enum FindingLevel {
Error
Warning
Info
} derive(Eq)

Validation severity.

#
Manifest

pub(all) struct Manifest {
name : String
version : String
entries : Array[Asset]
} derive(Eq)

A resource manifest. Entries are sorted only when exported, so callers may preserve discovery order during construction.

#
PathInfo

pub(all) struct PathInfo {
raw : String
normalized : String
segments : Array[String]
dirname : String
basename : String
stem : String
extension : String?
depth : Int
hidden : Bool
} derive(Eq)

#
PolicyAdvice

pub(all) struct PolicyAdvice {
path : String
current : CachePolicy
recommended : CachePolicy
reason : String
} derive(Eq)

#
PolicyProfile

pub(all) struct PolicyProfile {
total : Int
immutable : Int
revalidate : Int
no_store : Int
runtime : Int
aligned : Int
misaligned : Int
stability_score : Int
immutable_bytes : Int
revalidate_bytes : Int
no_store_bytes : Int
runtime_bytes : Int
} derive(Eq)

#
QualityReport

pub(all) struct QualityReport {
score : Int
risk : RiskLevel
entries : Int
total_bytes : Int
errors : Int
warnings : Int
generated_assets : Int
runtime_assets : Int
no_store_assets : Int
immutable_assets : Int
missing_tag_assets : Int
oversized_assets : Int
duplicate_fingerprint_assets : Int
directory_count : Int
extension_count : Int
policy_advice : Array[PolicyAdvice]
findings : Array[Finding]
} derive(Eq)

#
ReleaseDecision

pub(all) enum ReleaseDecision {
ReleasePass
ReleaseWarn
ReleaseBlock
} derive(Eq)

#
ReleaseDecision::to_wire

fn ReleaseDecision::to_wire(self : ReleaseDecision) -> String

#
ReleaseDelta

pub(all) struct ReleaseDelta {
added : Int
removed : Int
changed : Int
byte_delta : Int
policy_changes : Int
kind_changes : Int
fingerprint_changes : Int
tag_changes : Int
impact : ReleaseImpact
} derive(Eq)

#
ReleaseGate

pub(all) struct ReleaseGate {
min_score : Int
allow_warnings : Bool
max_warnings : Int
max_entries : Int
max_total_bytes : Int
max_asset_bytes : Int
require_tags : Bool
forbid_runtime : Bool
forbid_no_store : Bool
require_readme_asset : Bool
require_license_asset : Bool
} derive(Eq)

#
ReleaseGateResult

pub(all) struct ReleaseGateResult {
decision : ReleaseDecision
quality : QualityReport
blockers : Array[String]
warnings : Array[String]
notes : Array[String]
} derive(Eq)

#
ReleaseImpact

pub(all) enum ReleaseImpact {
ImpactNone
ImpactPatch
ImpactMinor
ImpactMajor
} derive(Eq)

#
ReleaseImpact::to_wire

fn ReleaseImpact::to_wire(self : ReleaseImpact) -> String

#
ReslockError

pub(all) enum ReslockError {
EmptyPath
AbsolutePath(String)
ParentTraversal(String)
InvalidPathSegment(String)
InvalidTag(String)
InvalidFingerprint(String)
DuplicatePath(String)
MissingHeader
InvalidLine(Int, String)
InvalidEscape(String)
BadNumber(String)
UnknownKind(String)
UnknownPolicy(String)
} derive(Eq)

Errors returned by parsers and constructors.

#
RiskLevel

pub(all) enum RiskLevel {
RiskLow
RiskMedium
RiskHigh
RiskCritical
} derive(Eq)

#
RiskLevel::to_wire

fn RiskLevel::to_wire(self : RiskLevel) -> String

#
SealSummary

pub(all) struct SealSummary {
entries : Int
total_bytes : Int
manifest_fingerprint : String
errors : Int
warnings : Int
} derive(Eq)

A compact, reproducible summary of a manifest.

#
aligned_cache_plans

fn aligned_cache_plans(manifest : Manifest) -> Array[CachePlan]

#
assess_quality

fn assess_quality(manifest : Manifest) -> QualityReport

#
asset

fn asset(path : String, content : String, kind? : AssetKind, policy? : CachePolicy, tags? : Array[String], note? : String) -> Result[Asset, ReslockError]

#
asset_bucket_name

fn asset_bucket_name(entry : Asset) -> String

#
asset_directory

fn asset_directory(entry : Asset) -> String

#
asset_extension

fn asset_extension(entry : Asset) -> String

#
assets_between_sizes

fn assets_between_sizes(manifest : Manifest, min_bytes : Int, max_bytes : Int) -> Array[Asset]

#
assets_larger_than

fn assets_larger_than(manifest : Manifest, min_bytes : Int) -> Array[Asset]

#
assets_requiring_revalidation

fn assets_requiring_revalidation(manifest : Manifest) -> Array[Asset]

#
assets_safe_for_immutable

fn assets_safe_for_immutable(manifest : Manifest) -> Array[Asset]

#
assets_smaller_than

fn assets_smaller_than(manifest : Manifest, max_bytes : Int) -> Array[Asset]

#
assets_under_directory

fn assets_under_directory(manifest : Manifest, directory : String) -> Array[Asset]

#
assets_with_bucket

fn assets_with_bucket(manifest : Manifest, bucket : AssetBucket) -> Array[Asset]

#
assets_with_extension

fn assets_with_extension(manifest : Manifest, extension : String) -> Array[Asset]

#
assets_with_kind

fn assets_with_kind(manifest : Manifest, kind : AssetKind) -> Array[Asset]

#
assets_with_policy

fn assets_with_policy(manifest : Manifest, policy : CachePolicy) -> Array[Asset]

#
assets_with_tag

fn assets_with_tag(manifest : Manifest, tag : String) -> Array[Asset]

#
assets_without_tags

fn assets_without_tags(manifest : Manifest) -> Array[Asset]

#
cache_plans

fn cache_plans(manifest : Manifest) -> Array[CachePlan]

#
cache_policy_weight

fn cache_policy_weight(policy : CachePolicy) -> Int

#
cache_stability_score

fn cache_stability_score(manifest : Manifest) -> Int

#
cache_tier

fn cache_tier(policy : CachePolicy) -> CacheTier

#
catalog_fingerprint_index

fn catalog_fingerprint_index(manifest : Manifest) -> Array[CountRow]

#
catalog_get_path

fn catalog_get_path(manifest : Manifest, path : String) -> Asset?

#
catalog_has_path

fn catalog_has_path(manifest : Manifest, path : String) -> Bool

#
catalog_manifest

fn catalog_manifest(manifest : Manifest) -> AssetCatalog

#
changed_paths_from_diff

fn changed_paths_from_diff(diffs : Array[EntryDiff]) -> Array[String]

#
classify_asset

fn classify_asset(entry : Asset) -> AssetBucket

#
count_by_bucket

fn count_by_bucket(manifest : Manifest) -> Array[CountRow]

#
count_by_directory

fn count_by_directory(manifest : Manifest) -> Array[CountRow]

#
count_by_extension

fn count_by_extension(manifest : Manifest) -> Array[CountRow]

#
count_by_kind

fn count_by_kind(manifest : Manifest) -> Array[CountRow]

#
count_by_policy

fn count_by_policy(manifest : Manifest) -> Array[CountRow]

#
count_by_tag

fn count_by_tag(manifest : Manifest) -> Array[CountRow]

#
default_release_gate

fn default_release_gate() -> ReleaseGate

#
demo_manifest

fn demo_manifest() -> Manifest

#
diff_manifest

fn diff_manifest(before : Manifest, after : Manifest) -> Array[EntryDiff]

#
directories_with_warnings

fn directories_with_warnings(manifest : Manifest) -> Array[DirectoryProfile]

#
directory_profiles

fn directory_profiles(manifest : Manifest) -> Array[DirectoryProfile]

#
duplicate_asset_count

fn duplicate_asset_count(manifest : Manifest) -> Int

#
duplicate_fingerprint_rows

fn duplicate_fingerprint_rows(manifest : Manifest) -> Array[CountRow]

#
evaluate_default_release_gate

fn evaluate_default_release_gate(manifest : Manifest) -> ReleaseGateResult

#
evaluate_release_gate

fn evaluate_release_gate(manifest : Manifest, gate : ReleaseGate) -> ReleaseGateResult

#
evaluate_strict_release_gate

fn evaluate_strict_release_gate(manifest : Manifest) -> ReleaseGateResult

#
fingerprint_fields

fn fingerprint_fields(fields : Array[String]) -> String

#
fingerprint_text

fn fingerprint_text(content : String) -> String

#
gate_requiring_metadata

fn gate_requiring_metadata(gate : ReleaseGate) -> ReleaseGate

#
gate_with_score

fn gate_with_score(gate : ReleaseGate, min_score : Int) -> ReleaseGate

#
gate_with_size_budget

fn gate_with_size_budget(gate : ReleaseGate, max_total_bytes : Int, max_asset_bytes : Int) -> ReleaseGate

#
is_valid

fn is_valid(manifest : Manifest) -> Bool

#
largest_assets

fn largest_assets(manifest : Manifest, limit : Int) -> Array[Asset]

#
manifest

fn manifest(name : String, version : String, entries : Array[Asset]) -> Manifest

#
manifest_directories

fn manifest_directories(manifest : Manifest) -> Array[String]

#
manifest_extensions

fn manifest_extensions(manifest : Manifest) -> Array[String]

#
manifest_has_license_asset

fn manifest_has_license_asset(manifest : Manifest) -> Bool

#
manifest_has_readme_asset

fn manifest_has_readme_asset(manifest : Manifest) -> Bool

#
manifest_release_notes

fn manifest_release_notes(before : Manifest, after : Manifest) -> String

#
manifest_tags

fn manifest_tags(manifest : Manifest) -> Array[String]

#
misaligned_cache_plans

fn misaligned_cache_plans(manifest : Manifest) -> Array[CachePlan]

#
newest_paths_from_diff

fn newest_paths_from_diff(diffs : Array[EntryDiff]) -> Array[String]

#
normalize_path

fn normalize_path(raw : String) -> Result[String, ReslockError]

#
normalize_tags

fn normalize_tags(tags : Array[String]) -> Array[String]

#
oversized_assets

fn oversized_assets(manifest : Manifest, threshold : Int) -> Array[Asset]

#
parse_asset_bucket

fn parse_asset_bucket(raw : String) -> AssetBucket

#
parse_kind

fn parse_kind(raw : String) -> Result[AssetKind, ReslockError]

#
parse_manifest

fn parse_manifest(input : String) -> Result[Manifest, ReslockError]

#
parse_policy

fn parse_policy(raw : String) -> Result[CachePolicy, ReslockError]

#
parse_risk_level

fn parse_risk_level(raw : String) -> RiskLevel

#
path_basename

fn path_basename(raw : String) -> Result[String, ReslockError]

#
path_depth

fn path_depth(raw : String) -> Result[Int, ReslockError]

#
path_dirname

fn path_dirname(raw : String) -> Result[String, ReslockError]

#
path_extension

fn path_extension(raw : String) -> Result[String?, ReslockError]

#
path_info

fn path_info(raw : String) -> Result[PathInfo, ReslockError]

#
path_is_archive_like

fn path_is_archive_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_changelog_like

fn path_is_changelog_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_config_like

fn path_is_config_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_document_like

fn path_is_document_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_generated_like

fn path_is_generated_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_hidden

fn path_is_hidden(raw : String) -> Result[Bool, ReslockError]

#
path_is_license_like

fn path_is_license_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_media_like

fn path_is_media_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_readme_like

fn path_is_readme_like(raw : String) -> Result[Bool, ReslockError]

#
path_is_source_like

fn path_is_source_like(raw : String) -> Result[Bool, ReslockError]

#
path_parent_chain

fn path_parent_chain(raw : String) -> Result[Array[String], ReslockError]

#
path_segments

fn path_segments(raw : String) -> Result[Array[String], ReslockError]

#
path_stem

fn path_stem(raw : String) -> Result[String, ReslockError]

#
path_with_extension

fn path_with_extension(raw : String, extension : String) -> Result[String, ReslockError]

#
path_without_extension

fn path_without_extension(raw : String) -> Result[String, ReslockError]

#
policy_advice_for_asset

fn policy_advice_for_asset(entry : Asset) -> PolicyAdvice?

#
policy_advice_for_manifest

fn policy_advice_for_manifest(manifest : Manifest) -> Array[PolicyAdvice]

#
policy_alignment_score

fn policy_alignment_score(manifest : Manifest) -> Int

#
policy_needs_review

fn policy_needs_review(manifest : Manifest) -> Bool

#
policy_profile

fn policy_profile(manifest : Manifest) -> PolicyProfile

#
profile_directory

fn profile_directory(directory : String, entries : Array[Asset]) -> DirectoryProfile

#
quality_findings

fn quality_findings(manifest : Manifest) -> Array[Finding]

#
quality_score

fn quality_score(manifest : Manifest, findings : Array[Finding]) -> Int

fn recommended_policy(entry : Asset) -> CachePolicy

#
release_delta_from_diffs

fn release_delta_from_diffs(diffs : Array[EntryDiff]) -> ReleaseDelta

#
release_gate_blocked

fn release_gate_blocked(result : ReleaseGateResult) -> Bool

#
release_gate_passed

fn release_gate_passed(result : ReleaseGateResult) -> Bool

#
release_gate_warned

fn release_gate_warned(result : ReleaseGateResult) -> Bool

#
release_impact

fn release_impact(added : Int, removed : Int, changed : Int, policy_changes : Int, kind_changes : Int) -> ReleaseImpact

#
removed_paths_from_diff

fn removed_paths_from_diff(diffs : Array[EntryDiff]) -> Array[String]

#
render_cache_plans

fn render_cache_plans(plans : Array[CachePlan]) -> String

#
render_catalog

fn render_catalog(catalog : AssetCatalog) -> String

#
render_count_rows

fn render_count_rows(rows : Array[CountRow]) -> String

#
render_diff

fn render_diff(diffs : Array[EntryDiff]) -> String

#
render_directory_profiles

fn render_directory_profiles(profiles : Array[DirectoryProfile]) -> String

#
render_findings

fn render_findings(findings : Array[Finding]) -> String

#
render_manifest

fn render_manifest(manifest : Manifest) -> String

#
render_path_info

fn render_path_info(info : PathInfo) -> String

#
render_policy_profile

fn render_policy_profile(profile : PolicyProfile) -> String

#
render_quality_report

fn render_quality_report(report : QualityReport) -> String

#
render_release_delta

fn render_release_delta(delta : ReleaseDelta) -> String

#
render_release_gate

fn render_release_gate(result : ReleaseGateResult) -> String

#
risk_from_score

fn risk_from_score(score : Int, findings : Array[Finding]) -> RiskLevel

#
sealed_asset

fn sealed_asset(path : String, kind : AssetKind, bytes : Int, fingerprint : String, policy : CachePolicy, tags? : Array[String], note? : String) -> Result[Asset, ReslockError]

#
smallest_assets

fn smallest_assets(manifest : Manifest, limit : Int) -> Array[Asset]

#
strict_release_gate

fn strict_release_gate() -> ReleaseGate

#
summarize

fn summarize(manifest : Manifest) -> SealSummary

#
summarize_release_delta

fn summarize_release_delta(before : Manifest, after : Manifest) -> ReleaseDelta

#
unique_asset_count

fn unique_asset_count(manifest : Manifest) -> Int

#
valid_tag

fn valid_tag(tag : String) -> Bool

#
validate_manifest

fn validate_manifest(manifest : Manifest) -> Array[Finding]

#
zero_byte_assets

fn zero_byte_assets(manifest : Manifest) -> Array[Asset]