xngg1021

research-object-identity

研究对象身份归一、聚合五态判定与来源追溯及谱系存证 (Provenance Tracking v1).

xngg1021 1 Updated 1mo ago

Resources

1
GitHub

Install

npx skillscat add xngg1021/academic-research-kernel/research-object-identity

Install via the SkillsCat registry.

SKILL.md

research-object-identity

Research Object Identity & Provenance Kernel v1:研究对象身份判定与科研生产谱系的确定性公共骨架。本技能包含两大核心子系统:

  1. 身份与版本关系层(`scripts/identity.py`):标识符归一(normalize)、候选记录聚合判定(resolve)、关系与版本谱系建边(link)。
  2. 来源追溯与谱系存证(Provenance Tracking v1)(`scripts/provenance.py`):借鉴 W3C PROV-DM 规范,确立计算活动(Activity)与产物实体(Entity)为一等公民,提供严格因果 DAG 校验与毫秒级脱机反向溯源,产出符合 `../../schemas/lineage-receipt.schema.json` 的确定性存证回执。

纯标准库、无网络依赖、无外部大模型调用。判定与验证结论一律为离散字段,严禁任何置信分数与模糊打分。

When to Use

  • 同一篇论文、数据集或代码仓库来自多个来源,需要判定是否为同一对象;
  • 需要把 preprint、正式发表版、更正和撤稿串接成版本谱系链条;
  • 需要追踪数据清洗、统计分析、表格提取全过程,明确“某个论文表格数据项究竟由哪份原始数据、哪个 commit 的代码、哪次计算活动与哪些参数产生”;
  • 需要对研究生产链条执行因果 DAG 无环性校验与基于 SHA256 的防篡改完整性验证。

对象与实体模型

1. Research Object (身份层)

五类对象:Work、Person、Dataset、CodeRepository、Artifact。每个对象八个顶层字段:object_id、object_type、identifiers、manifestations、relations、lineage、source_observations、uncertainty。

2. Provenance Kernel (生产谱系层)

  • Entity(实体):data_snapshot, code_file, environment_spec, statistic_artifact, table_cell, figure_artifact, generic_entity。包含唯一标识 id、类型 type、内容哈希 sha256(64位小写十六进制)、定位符 locator 及元数据。
  • Activity(活动):data_cleaning, computation_run, statistical_analysis, table_extraction, render_run, generic_activity。记录具体执行命令 command、脚本实体引用 script_id、代码版本 commit_sha、参数映射字典 parameters 及环境指纹 environment。
  • LineageEdge(因果关系边):
    • used: 活动消费输入实体(Activity -> Entity);
    • generated: 活动产出实体(Activity -> Entity,遵循单产生者约束);
    • derived_from: 实体间因果派生(Derived Entity -> Source Entity,可选绑定媒介 Activity)。

工作流一:标识符归一(normalize)

normalize(kind, value) -> str,每类标识符一套确定性规则:

kind 规则 示例
doi 去 https://doi.org/、doi: 前缀并小写 https://doi.org/10.1038/Nature12373 → 10.1038/nature12373
arxiv_id 去 URL 与 arXiv: 前缀、去版本号 2310.15264v2 → 2310.15264
pmid 只保留数字 PMID: 38123654 → 38123654
openalex_id 从 URL 取末段 ID https://openalex.org/W123 → W123
orcid 去 URL 与连字符取纯数字 https://orcid.org/0000-0002-1825-0097 → 0000000218250097

工作流二:聚合判定(resolve)

resolve(records) 输入若干同一对象的候选记录,输出 object_type、merged_identifiers、verdict、match_fields、conflict_fields、sources 与 human_confirmed。按优先级判定:

verdict 含义 触发条件
CONFLICT 候选记录互相冲突 同一标识符类型出现两个不同归一值
EXACT 确认同一对象 至少一个规范标识符跨记录一致
STRONG_MATCH 强匹配 无共享标识符,但规范标题与作者全部一致
CANDIDATE 疑似同一对象 无共享标识符,标题 Jaccard 相似度 ≥ 0.5,或作者重叠
UNRESOLVED 无法判断 信息不足(空记录或单记录且无标识符)

工作流三:建边(link)

link(a, b, kind, evidence) 在 a 到 b 之间建立有向边:

  • relations(cites、contradicts、replicates、derived_from):单向记录在 a 侧;
  • lineage(preprint_to_vor、correction、retraction、version_chain):双向回填,保留方向。
    evidence 必须包含:source、queried_at、match_fields、conflict_fields、human_confirmed 五项。

工作流四:来源追溯与可重放回执 (Provenance Tracking)

使用 provenance 模块构建全流程生产谱系网络:

# fragment: true
from provenance import LineageGraph, validate_lineage, trace_origin

graph = LineageGraph(root_dir=".")

# 注册实体与活动 (强制互斥命名空间与幂等注册)
e_raw = graph.add_entity("raw_data", "data_snapshot", sha256="...", locator="data/raw.csv")
e_script = graph.add_entity("clean_script", "code_file", sha256="...", locator="scripts/clean.py")
e_clean = graph.add_entity("clean_data", "data_snapshot", sha256="...", locator="data/clean.csv")

act = graph.add_activity("clean_act", "data_cleaning", command="python scripts/clean.py", script_id=e_script.id)

# 记录因果关系
graph.record_used(act.id, e_raw.id)
graph.record_used(act.id, e_script.id)
graph.record_generated(act.id, e_clean.id)

# 拓扑与内容完整性校验
v_stat, t_stat, c_stat, err = validate_lineage(graph, check_on_disk_hashes=True)

# 逆向毫秒级溯源,生成不可变 LineageReceipt
receipt = trace_origin(graph, target_id=e_clean.id)
  • 状态区分与覆盖率(拒绝将 UNKNOWN 折叠为 intact):intact(验证完整通过,所有声明哈希的本地文件物理字节一致)、partial(部分本地文件校验通过,存在未在本地解析的远程 URI 实体)、unchecked(未开启哈希检查或全为无哈希/未本地验证实体)、missing_artifact(声明了哈希但本地文件丢失)、hash_mismatch(物理文件哈希与声明不符)、cycle_detected(因果拓扑环路)、missing_input / broken_chain(引用不存在的实体或活动)。
  • 不可变回执存证:生成深拷贝的 LineageReceipt,因果图拓扑指纹(lineage_digest)与回执审计指纹(receipt_digest / receipt_id)清晰分离,同图同状态绝对稳定确定。
  • 声称与验证边界:所记录的代码 Commit SHA 与参数字典代表执行声明(claimed commit identity),供离线审计核对。

边界

  • 不评分:任何字段都不是概率或置信度,判定依据只有离散字段比对;
  • 不联网:归一、判定、建边与溯源全部离线可重放;
  • 不伪造阴性状态:未检查或不可观测状态显式标记为 unchecked 或 unobservable,绝不冒充 intact。

Verification

离线冒烟测试验证:

# smoke-test: true
import os, sys
sys.path.insert(0, os.path.join(os.environ.get('SKILL_DIR', os.getcwd()), 'scripts'))
import identity as rid
import provenance as pr

# 1. 归一测试
assert rid.normalize('doi', 'https://doi.org/10.1038/Nature12373') == '10.1038/nature12373'
assert rid.normalize('arxiv_id', '2310.15264v2') == '2310.15264'

# 2. 五态判定测试
exact = rid.resolve([
    {'identifiers': [{'type': 'doi', 'value': '10.1/a'}], 'title': 'T', 'authors': ['A, B'], 'year': 2020},
    {'identifiers': [{'type': 'doi', 'value': 'doi:10.1/A'}], 'title': 'T', 'authors': ['A, B'], 'year': 2020}])
assert exact['verdict'] == 'EXACT' and exact['human_confirmed'] is False

# 3. 来源追溯与谱系存证冒烟测试
graph = pr.LineageGraph()
e_in = graph.add_entity("raw_1", "data_snapshot")
e_out = graph.add_entity("clean_1", "data_snapshot")
act = graph.add_activity("act_1", "data_cleaning")
graph.record_used(act.id, e_in.id)
graph.record_generated(act.id, e_out.id)
graph.record_derivation(e_out.id, e_in.id, activity_id=act.id)

v_stat, t_stat, c_stat, err = pr.validate_lineage(graph, check_on_disk_hashes=False)
assert v_stat == "unchecked" and t_stat == "valid_dag"

rec = pr.trace_origin(graph, target_id=e_out.id, check_on_disk_hashes=False)
assert rec.topology_status == "valid_dag"
assert rec.root_ancestors == ("raw_1",)
assert len(rec.trace_steps) == 1

print('research-object-identity smoke PASS')