Sign in

    moonshacl

    Auditable RDF graph contracts and atomic update validation in pure MoonBit

    rdf
    shacl
    validation
    transaction
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    8 hours ago
    Downloads
    6

    Dependencies

    #MoonSHACL

    CI

    MoonBit 原生 RDF 图契约编译与事务校验库。 读取标准 SHACL shapes graph,检查跨节点关系和属性路径,并在图更新前进行校验。失败的更新保留原图,报告指出违反规则的节点、路径和来源 shape。

    这是基于 2017 SHACL Recommendation 的受限 profile,不是完整 SHACL Core/SPARQL 处理器。不支持的规则会在编译阶段返回错误;不会忽略约束后报告“通过”。

    #适合什么需求

    MoonTTL 已提供 MoonBit 的 RDF 解析层,MoonSHACL 补充解析之后的图约束执行层。适用于需要 MoonBit 同进程嵌入的 RDF 导入、共享对象引用检查和数据更新预检。

    例如 Alice 和 Bob 都引用同一家公司的名称。删除公司的名称会同时使两个人违反 (employer name) 路径约束;直接替换名称则可以作为一个原子更新通过。普通 JSON 字段校验无法直接表达这种共享节点关系。

    真实标准样本采用 SEMIC 官方 DCAT-AP 3.0.0 base shapes。欧盟已有 SHACL 验证服务,证明图约束有实际用途。本项目尚无外部用户采用证据。

    #运行

    需要 MoonBit 工具链、Node.js 和 native 测试所需的 C 编译器。当前验证环境:Moon 0.1.20260920、moonc 0.10.14、Node 24.21.0、Windows/MSVC。scripts/moon.mjs 优先使用工作区 .tools/moon,否则使用 PATH 中的 moon。工具链安装见 MoonBit 官方文档。

    node scripts/moon.mjs update npm ci --ignore-scripts npm run build node cli.mjs validate examples/people-shapes.ttl examples/people-data.ttl

    CLI 运行只需要 Node 内置模块和编译后的 dist/moonshacl.mjs。N3、rdf-validate-shacl、shacl-engine 是测试依赖,产品不会调用这些验证器。

    # 只检查规则是否属于支持范围 node cli.mjs audit examples/people-shapes.ttl # 删除共享公司名称:拒绝更新,退出码 1 node cli.mjs update examples/people-shapes.ttl examples/people-data.ttl examples/empty.ttl examples/remove-shared-name.ttl # 删除旧名称并加入新名称:原子提交,退出码 0 node cli.mjs update examples/people-shapes.ttl examples/people-data.ttl examples/add-shared-name.ttl examples/remove-shared-name.ttl # 使用未经修改的官方规则 node cli.mjs validate fixtures/dcat-ap-3.0.0.ttl examples/dcat-data.ttl # 标准 RDF 报告,N-Triples 输出 node cli.mjs validate examples/people-shapes.ttl examples/people-data.ttl --rdf

    退出码:0 表示通过/已提交;1 表示不符合约束/更新被拒绝;2 表示请求、解析或规则范围错误。update 返回内存中候选校验的结果和最终图,不会改写输入文件。文本增删只接受 IRI 主语与 IRI/字面量宾语;空白节点更新使用库 API 的精确节点身份。

    #MoonBit API

    模块名:wangjiale6036-dotcom/moonshacl;Turtle 适配包:wangjiale6036-dotcom/moonshacl/turtle;当前版本 0.1.0。GitHub 源码可按上面的步骤构建;公开包消费示例见 examples/consumer。

    let shapes = @turtle.parse(shapes_text, scope="shapes-") |> Result::unwrap
    let data = @turtle.parse(data_text, scope="data-") |> Result::unwrap
    let plan = @moonshacl.compile(shapes) |> Result::unwrap
    let report = plan.validate(data)
    let session = @moonshacl.Session::new(plan, data)
    let preview = session.preview(additions, removals)
    let update = session.apply(additions, removals)

    实际应用应处理解析和编译的 Err;unwrap 仅用于短示例。additions/removals 类型为 Array[Triple]。compile 返回 Result[Plan, Array[Problem]],与不符合约束的 Report 分开。

    同一原生调用示例可在三个后端运行:

    node scripts/moon.mjs run src/demo --target js node scripts/moon.mjs run src/demo --target wasm-gc node scripts/moon.mjs run src/demo --target native

    Plan 可以复用;validate_node 可指定 shape/focus node;Report.to_json() 与 to_graph() 分别输出 JSON 和标准 RDF 报告。图采用集合语义并保留 RDF 字面量的词法形式、datatype 和 language。对外返回的数组与路径不允许间接改写内部计划/缓存。

    #支持范围

    四类标准 target;谓词、逆向、序列、备选及三种重复路径;类、datatype、nodeKind、数量、字符串长度、语言、集合、逻辑组合、嵌套 shape、equals/disjoint、closed 和 qualified 数量约束。具体参数、datatype 和拒绝项见 PROFILE.md。

    不支持 SPARQL、JS/AF、自定义约束组件、递归 shapes、自动 imports、推理服务、pattern/flags、数值范围与 lessThan 比较。日期等未实现词法校验的 datatype 会被拒绝。类检查只沿数据图明确给出的 rdf:type 和 rdfs:subClassOf 边,不做完整 RDFS/OWL 推理。

    更新缓存记录实际图读取,包含查不到值的读取,并对变化涉及的 focus nodes 复验。每次更新仍会重建候选图并重算 targets;这不是数据库增量引擎,也没有性能胜过现有方案的实测结论。Session 无并发/持久化事务语义。

    Turtle 适配复用 thy1016/moonttl@0.3.0 的语法解析和 materializer,并修正该版本相邻标点与 bare a 的兼容问题,详见 THIRD_PARTY.md。不验证部分解析结果。

    #验证

    npm test

    该命令执行格式检查、编译检查、三个后端测试与原生调用示例、CLI/JSON 边界检查及独立验证器对照。测试完成后即可离线运行,官方 fixtures 已固定在仓库中,测试不需要抓取在线数据。

    当前结果:三个后端各 21 个测试通过;98 个 W3C validation fixtures 中 76 个通过、22 个明确拒绝、0 个已执行语义失败;每个后端 1,000 次确定性更新提案与全量复验一致;13 个 CLI 检查、17 个 JSON 边界检查、14 个独立引擎场景通过。这些数字不表示完整 SHACL 认证。

    用例来源、逐项拒绝原因和对照字段见 VALIDATION.md。源码包保留 tests 和第三方许可,调查快照 audit/ 不进入 Mooncakes 包。

    三个端到端场景与验收产物见 SCENARIOS.md。独立消费模块在三个后端验证公开 API;node scripts/release-smoke.mjs 检查发布包脱离开发目录运行。源码统计脚本排除测试、生成 fixtures 和宿主层:2,020 行去空白/注释,1,539 行再去纯分隔符。可审查记录见 docs/evidence,统计口径不代替赛事认定。

    精简八项 项目申报书 与 维护发行流程 一并提供。

    #选题与已有方案

    当前 Mooncakes 公开索引与相邻模块调查尚未发现同功能 MoonBit SHACL 引擎;未发布仓库及全部历史源码不在穷举范围。已有 pySHACL、RDF/JS 与 RDF4J 方案具备更全面的能力,事务/增量、CLI、浏览器和离线运行都不是本项目的全球创新点。

    独立贡献在于 MoonBit 原生的 RDF targets/paths/约束执行、支持范围编译检查、可追踪标准报告和图更新预检,并与已有 MoonTTL 解析层直接集成。需要完整 SHACL 或允许外部服务的系统可选成熟方案。完整选题证据与查重范围见 选题依据。

    原始实现采用 Apache-2.0;第三方 tests、官方 shapes、依赖库分别保留其许可证与署名,见 THIRD_PARTY.md。

    Cached

    type Cached

    Graph

    pub struct Graph {
    triples : Array[Triple]
    spo : Map[(Term, String), Array[Term]]
    pos : Map[(String, Term), Array[Term]]
    }

    Graphs use RDF set semantics: duplicate triples never count twice.

    Graph::new

    fn Graph::new(triples : Array[Triple]) -> Graph

    Graph::objects

    fn Graph::objects(self : Graph, subject : Term, predicate : String) -> Array[Term]

    Graph::size

    fn Graph::size(self : Graph) -> Int

    Graph::subjects

    fn Graph::subjects(self : Graph, predicate : String, object : Term) -> Array[Term]

    Graph::to_ntriples

    fn Graph::to_ntriples(self : Graph) -> String

    Graph::triples

    fn Graph::triples(self : Graph) -> Array[Triple]

    Graph::values

    fn Graph::values(self : Graph, start : Term, path : Path) -> Array[Term]

    Evaluate a compiled path against an immutable graph.

    Path

    pub(all) enum Path {
    Predicate(String)
    Inverse(Path)
    Sequence(Array[Path])
    Alternative(Array[Path])
    ZeroOrMore(Path)
    OneOrMore(Path)
    ZeroOrOne(Path)
    } derive(Eq,
    Debug
    )

    Path::equal

    fn Path::equal(Path, Path) -> Bool

    Path::not_equal

    fn Path::not_equal(x : Path, y : Path) -> Bool

    Path::to_json

    fn Path::to_json(self : Path) -> Json

    Path::to_repr

    Plan

    pub struct Plan {
    shapes : Map[Term, Shape]
    shape_order : Array[Term]
    parents : Map[Term, Array[Term]]
    }

    Compiled shapes are reusable and isolated from caller-owned arrays.

    Plan::validate

    fn Plan::validate(self : Plan, graph : Graph) -> Report

    Validation includes all severities in the W3C conforms flag.

    Plan::validate_node

    fn Plan::validate_node(self : Plan, graph : Graph, shape : Term, focus : Term) -> Result[Report, String]

    Explicit focus validation does not depend on target declarations.

    Problem

    pub(all) struct Problem {
    node : Term
    predicate : String
    message : String
    } derive(Eq,
    Debug
    )

    Shape compilation errors are not validation results.

    Problem::equal

    fn Problem::equal(Problem, Problem) -> Bool

    Problem::not_equal

    fn Problem::not_equal(x : Problem, y : Problem) -> Bool

    Problem::to_json

    fn Problem::to_json(self : Problem) -> Json

    Problem::to_repr

    Report

    pub(all) struct Report {
    conforms : Bool
    results : Array[ValidationResult]
    checked : Int
    reused : Int
    }

    Report::to_graph

    fn Report::to_graph(self : Report) -> Graph

    so generated report/path nodes cannot alias caller-supplied blank-node labels.

    Report::to_json

    fn Report::to_json(self : Report) -> Json

    Session

    pub struct Session {
    plan : Plan
    graph : Graph
    cache : Map[(Term, Term), Cached]
    }

    A session keeps validation state for one immutable compiled plan.

    Session::apply

    fn Session::apply(self : Session, additions : Array[Triple], removals : Array[Triple]) -> Update

    Deletes are applied before additions. A rejected update changes neither graph nor cache.

    Session::graph

    fn Session::graph(self : Session) -> Graph

    Session::new

    fn Session::new(plan : Plan, graph : Graph) -> Session

    The initial graph may be invalid; only conforming updates can be committed.

    Session::preview

    fn Session::preview(self : Session, additions : Array[Triple], removals : Array[Triple]) -> Report

    Inspect a proposed graph without changing graph or cache, including on success.

    Session::report

    fn Session::report(self : Session) -> Report

    Shape

    type Shape

    Term

    pub(all) enum Term {
    Iri(String)
    Blank(String)
    Literal(String, String, String?)
    } derive(
    Debug
    )

    A term's identity includes its lexical spelling and datatype, never its coerced value.
    impl Eq for Term
    impl Hash for Term

    Term::equal

    fn Term::equal(self : Term, other : Term) -> Bool

    Term::hash

    fn Term::hash(self : Term) -> Int

    Term::hash_combine

    fn Term::hash_combine(self : Term, hasher : Hasher) -> Unit

    Term::not_equal

    fn Term::not_equal(x : Term, y : Term) -> Bool

    Term::to_json

    fn Term::to_json(self : Term) -> Json

    Stable JSON encoding preserves RDF term identity, including ill-typed literals.

    Term::to_ntriples

    fn Term::to_ntriples(self : Term) -> String

    Term::to_repr

    Triple

    pub(all) struct Triple {
    subject : Term
    predicate : String
    object : Term
    } derive(Eq, Hash,
    Debug
    )

    Triple::equal

    fn Triple::equal(Triple, Triple) -> Bool

    Triple::hash

    fn Triple::hash(self : Triple) -> Int

    Triple::hash_combine

    fn Triple::hash_combine(Triple, Hasher) -> Unit

    Triple::not_equal

    fn Triple::not_equal(x : Triple, y : Triple) -> Bool

    Triple::to_repr

    Update

    pub(all) struct Update {
    committed : Bool
    report : Report
    }

    ValidationResult

    pub(all) struct ValidationResult {
    focus_node : Term
    source_shape : Term
    component : String
    severity : Term
    path : Path?
    value : Term?
    messages : Array[Term]
    details : Array[ValidationResult]
    } derive(Eq,
    Debug
    )

    ValidationResult::equal

    ValidationResult::not_equal

    fn ValidationResult::not_equal(x : ValidationResult, y : ValidationResult) -> Bool

    ValidationResult::to_json

    fn ValidationResult::to_json(self : ValidationResult) -> Json

    compile

    fn compile(graph : Graph) -> Result[Plan, Array[Problem]]

    Unsupported semantics cause Err before any data validation is attempted.

    lang_literal

    fn lang_literal(value : String, language : String) -> Term

    literal

    fn literal(value : String) -> Term

    rdf

    let rdf : String

    rdfs

    let rdfs : String

    let sh : String

    supported_parameters

    fn supported_parameters() -> Array[String]

    Exposes the capability contract, not a claim of full SHACL Core conformance.

    xsd

    let xsd : String