From 63271ac957545dc1614f57fb876fe81d8990e308 Mon Sep 17 00:00:00 2001 From: tuanadr Date: Wed, 20 May 2026 17:59:18 +0700 Subject: [PATCH] Add dataset curation reuse credit ledger --- .../README.md | 43 +++++ .../acceptance-notes.md | 27 +++ .../demo-output/demo.mp4 | Bin 0 -> 43148 bytes .../demo-output/demo.svg | 57 +++++++ .../demo-output/reuse-credit-ledger.json | 101 +++++++++++ dataset-curation-reuse-credit-ledger/demo.js | 83 +++++++++ dataset-curation-reuse-credit-ledger/index.js | 159 ++++++++++++++++++ .../requirements-map.md | 15 ++ .../sample-data.js | 54 ++++++ dataset-curation-reuse-credit-ledger/test.js | 123 ++++++++++++++ 10 files changed, 662 insertions(+) create mode 100644 dataset-curation-reuse-credit-ledger/README.md create mode 100644 dataset-curation-reuse-credit-ledger/acceptance-notes.md create mode 100644 dataset-curation-reuse-credit-ledger/demo-output/demo.mp4 create mode 100644 dataset-curation-reuse-credit-ledger/demo-output/demo.svg create mode 100644 dataset-curation-reuse-credit-ledger/demo-output/reuse-credit-ledger.json create mode 100644 dataset-curation-reuse-credit-ledger/demo.js create mode 100644 dataset-curation-reuse-credit-ledger/index.js create mode 100644 dataset-curation-reuse-credit-ledger/requirements-map.md create mode 100644 dataset-curation-reuse-credit-ledger/sample-data.js create mode 100644 dataset-curation-reuse-credit-ledger/test.js diff --git a/dataset-curation-reuse-credit-ledger/README.md b/dataset-curation-reuse-credit-ledger/README.md new file mode 100644 index 0000000..cb58285 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/README.md @@ -0,0 +1,43 @@ +# Dataset Curation Reuse Credit Ledger + +This is a focused Community & User Reputation System slice for SCIBASE issue #15. It awards or quarantines reputation credit for downstream reuse of curated datasets and code, using citation evidence, license compatibility, self-reuse controls, duplicate evidence checks, and deterministic audit digests. + +## Scope + +- Allocates curator, validator, and maintainer credit from validated downstream reuse. +- Requires citation evidence before awarding public reputation. +- Checks license compatibility between source asset and reuse event. +- Quarantines self-reuse until independent evidence exists. +- Flags duplicate evidence hashes. +- Emits profile-ready reputation deltas and reuse badges. +- Emits stable `dcrcl_` audit digests for each reuse packet. + +It intentionally does not duplicate broad reputation ledgers, review-quality safeguards, abuse/appeals detectors, peer-review calibration, reviewer COI assignment, contributor credit attestation, transparency receipts, correction-impact ledgers, or mentorship impact ladders. + +## Run + +```powershell +node dataset-curation-reuse-credit-ledger/test.js +node dataset-curation-reuse-credit-ledger/demo.js +``` + +The demo writes: + +- `dataset-curation-reuse-credit-ledger/demo-output/reuse-credit-ledger.json` +- `dataset-curation-reuse-credit-ledger/demo-output/demo.svg` + +This PR also includes the required short MP4 demo artifact: + +- `dataset-curation-reuse-credit-ledger/demo-output/demo.mp4` + +## API + +```js +const { + auditReuseCredits, + buildReputationSummary, + createReuseDigest, +} = require("./dataset-curation-reuse-credit-ledger"); + +const audit = auditReuseCredits({ assets, reuseEvents, generatedAt }); +``` diff --git a/dataset-curation-reuse-credit-ledger/acceptance-notes.md b/dataset-curation-reuse-credit-ledger/acceptance-notes.md new file mode 100644 index 0000000..b5643e6 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/acceptance-notes.md @@ -0,0 +1,27 @@ +# Acceptance Notes + +## What This Adds + +- Dependency-free Node.js module under `dataset-curation-reuse-credit-ledger/`. +- Deterministic reuse-credit audit packets for curated datasets and code. +- Tests for valid credit allocation, unsafe evidence quarantine, profile-ready reputation summaries, and stable reuse digests. +- Demo JSON, SVG, and MP4 artifacts for bounty review. + +## Verification + +Use these commands from the repository root: + +```powershell +node dataset-curation-reuse-credit-ledger/test.js +node dataset-curation-reuse-credit-ledger/demo.js +node --check dataset-curation-reuse-credit-ledger/index.js +node --check dataset-curation-reuse-credit-ledger/test.js +node --check dataset-curation-reuse-credit-ledger/demo.js +node --check dataset-curation-reuse-credit-ledger/sample-data.js +ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height -of default=noprint_wrappers=1 dataset-curation-reuse-credit-ledger/demo-output/demo.mp4 +git diff --check +``` + +## AI Assistance Disclosure + +This contribution was prepared with AI assistance from OpenAI Codex and reviewed through local deterministic tests and artifact checks before submission. diff --git a/dataset-curation-reuse-credit-ledger/demo-output/demo.mp4 b/dataset-curation-reuse-credit-ledger/demo-output/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..80124f92bc074e56e36130a734ddda2071e63d32 GIT binary patch literal 43148 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 k0S7vnkY + + + Dataset Curation Reuse Credit Ledger + Community reputation from downstream dataset and code reuse + + + 3 + Reuse + + + + 2 + Awarded + + + + 1 + Quarantine + + + + 1 + License + + + + + reuse-1 - Single-cell assay benchmark + Award Credit + Credit awarded + dcrcl_c48af1209078c13469ce3a4d + + + + reuse-2 - Single-cell assay benchmark + Quarantine Credit + MISSING_CITATION | LICENSE_MISMATCH | SELF_REUSE | DUPLICATE_EVIDENCE + dcrcl_1098b9741d89e72ac0581b45 + + + + reuse-3 - QC pipeline + Award Credit + Credit awarded + dcrcl_bca70185e357d2e0deffdc38 + + u-alina +21 u-minh +24 + \ No newline at end of file diff --git a/dataset-curation-reuse-credit-ledger/demo-output/reuse-credit-ledger.json b/dataset-curation-reuse-credit-ledger/demo-output/reuse-credit-ledger.json new file mode 100644 index 0000000..58c9021 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/demo-output/reuse-credit-ledger.json @@ -0,0 +1,101 @@ +{ + "generatedAt": "2026-05-20T13:30:00.000Z", + "reusePackets": [ + { + "eventId": "reuse-1", + "assetId": "dataset-cell-42", + "assetTitle": "Single-cell assay benchmark", + "assetType": "dataset", + "generatedAt": "2026-05-20T13:30:00.000Z", + "decision": "award_credit", + "flags": [], + "creditAllocations": [ + { + "userId": "u-alina", + "assetId": "dataset-cell-42", + "points": 21, + "reason": "curator reuse credit" + }, + { + "userId": "u-minh", + "assetId": "dataset-cell-42", + "points": 9, + "reason": "validator reuse credit" + } + ], + "actions": [ + "Publish reuse credit to curator profiles" + ], + "reuseDigest": "dcrcl_c48af1209078c13469ce3a4d" + }, + { + "eventId": "reuse-2", + "assetId": "dataset-cell-42", + "assetTitle": "Single-cell assay benchmark", + "assetType": "dataset", + "generatedAt": "2026-05-20T13:30:00.000Z", + "decision": "quarantine_credit", + "flags": [ + "MISSING_CITATION", + "LICENSE_MISMATCH", + "SELF_REUSE", + "DUPLICATE_EVIDENCE" + ], + "creditAllocations": [], + "actions": [ + "Add citation evidence for reuse-2", + "Resolve CC0 vs CC-BY-4.0 license mismatch", + "Require independent reuse evidence before awarding curator reputation", + "Deduplicate evidence hash sha256:evidence-1" + ], + "reuseDigest": "dcrcl_1098b9741d89e72ac0581b45" + }, + { + "eventId": "reuse-3", + "assetId": "code-qc-7", + "assetTitle": "QC pipeline", + "assetType": "code", + "generatedAt": "2026-05-20T13:30:00.000Z", + "decision": "award_credit", + "flags": [], + "creditAllocations": [ + { + "userId": "u-minh", + "assetId": "code-qc-7", + "points": 15, + "reason": "maintainer reuse credit" + } + ], + "actions": [ + "Publish reuse credit to curator profiles" + ], + "reuseDigest": "dcrcl_bca70185e357d2e0deffdc38" + } + ], + "reputationSummary": { + "counts": { + "reuseEvents": 3, + "awarded": 2, + "quarantined": 1, + "duplicateEvidence": 1, + "licenseIssues": 1 + }, + "reputationDeltas": [ + { + "userId": "u-alina", + "points": 21, + "badges": [ + "dataset_reuse_curator" + ] + }, + { + "userId": "u-minh", + "points": 24, + "badges": [ + "code_reuse_maintainer", + "dataset_reuse_validator" + ] + } + ] + } +} diff --git a/dataset-curation-reuse-credit-ledger/demo.js b/dataset-curation-reuse-credit-ledger/demo.js new file mode 100644 index 0000000..c765fde --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/demo.js @@ -0,0 +1,83 @@ +const fs = require("fs"); +const path = require("path"); + +const { auditReuseCredits } = require("./index"); +const { assets, reuseEvents } = require("./sample-data"); + +const generatedAt = "2026-05-20T13:30:00.000Z"; +const outputDir = path.join(__dirname, "demo-output"); + +fs.mkdirSync(outputDir, { recursive: true }); + +const audit = auditReuseCredits({ assets, reuseEvents, generatedAt }); +fs.writeFileSync(path.join(outputDir, "reuse-credit-ledger.json"), `${JSON.stringify(audit, null, 2)}\n`); +fs.writeFileSync(path.join(outputDir, "demo.svg"), buildSvg(audit)); + +console.log("Dataset curation reuse credit ledger demo"); +console.log(`Reuse events: ${audit.reputationSummary.counts.reuseEvents}`); +console.log(`Awarded: ${audit.reputationSummary.counts.awarded}`); +console.log(`Quarantined: ${audit.reputationSummary.counts.quarantined}`); +console.log(`License issues: ${audit.reputationSummary.counts.licenseIssues}`); +console.log(`Wrote ${path.join(outputDir, "reuse-credit-ledger.json")}`); +console.log(`Wrote ${path.join(outputDir, "demo.svg")}`); + +function buildSvg(audit) { + const rows = audit.reusePackets.map((packet, index) => { + const y = 196 + index * 82; + const color = packet.decision === "award_credit" ? "#1f8a5b" : "#b42318"; + const flags = packet.flags.length === 0 ? "Credit awarded" : packet.flags.join(" | "); + return ` + + + ${escapeXml(packet.eventId)} - ${escapeXml(packet.assetTitle)} + ${escapeXml(formatDecision(packet.decision))} + ${escapeXml(flags)} + ${escapeXml(packet.reuseDigest)} + `; + }).join(""); + + const deltas = audit.reputationSummary.reputationDeltas + .map((delta) => `${delta.userId} +${delta.points}`) + .join(" "); + + return ` + + + Dataset Curation Reuse Credit Ledger + Community reputation from downstream dataset and code reuse + ${metricCard(64, 112, "Reuse", audit.reputationSummary.counts.reuseEvents, "#0b5fff")} + ${metricCard(252, 112, "Awarded", audit.reputationSummary.counts.awarded, "#1f8a5b")} + ${metricCard(440, 112, "Quarantine", audit.reputationSummary.counts.quarantined, "#b42318")} + ${metricCard(628, 112, "License", audit.reputationSummary.counts.licenseIssues, "#ad6f00")} + ${rows} + ${escapeXml(deltas)} +`; +} + +function metricCard(x, y, label, value, color) { + return ` + + ${value} + ${escapeXml(label)} + `; +} + +function formatDecision(decision) { + return decision.split("_").map((part) => part[0].toUpperCase() + part.slice(1)).join(" "); +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} diff --git a/dataset-curation-reuse-credit-ledger/index.js b/dataset-curation-reuse-credit-ledger/index.js new file mode 100644 index 0000000..837d0d7 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/index.js @@ -0,0 +1,159 @@ +const crypto = require("crypto"); + +const BASE_POINTS = { + replicated: 30, + validated: 15, + unverified: 5, +}; + +function auditReuseCredits({ assets, reuseEvents, generatedAt = new Date().toISOString() }) { + if (!Array.isArray(assets)) { + throw new Error("assets must be an array"); + } + if (!Array.isArray(reuseEvents)) { + throw new Error("reuseEvents must be an array"); + } + + const assetById = new Map(assets.map((asset) => [asset.id, asset])); + const seenEvidenceHashes = new Set(); + const reusePackets = reuseEvents.map((event) => { + const asset = assetById.get(event.assetId); + if (!asset) { + throw new Error(`Unknown asset '${event.assetId}'`); + } + const packet = evaluateReuseEvent(event, asset, seenEvidenceHashes, generatedAt); + if (event.evidenceHash) { + seenEvidenceHashes.add(event.evidenceHash); + } + return packet; + }); + + const audit = { + generatedAt, + reusePackets, + }; + audit.reputationSummary = buildReputationSummary(audit); + return audit; +} + +function evaluateReuseEvent(event, asset, seenEvidenceHashes, generatedAt) { + const flags = []; + if (!String(event.citation || "").trim()) { + flags.push("MISSING_CITATION"); + } + if (event.licenseUsed !== asset.license) { + flags.push("LICENSE_MISMATCH"); + } + const maintainerIds = new Set((asset.maintainers || []).map((maintainer) => maintainer.userId)); + if (maintainerIds.has(event.reuserId)) { + flags.push("SELF_REUSE"); + } + if (event.evidenceHash && seenEvidenceHashes.has(event.evidenceHash)) { + flags.push("DUPLICATE_EVIDENCE"); + } + + const decision = flags.length === 0 ? "award_credit" : "quarantine_credit"; + const creditAllocations = decision === "award_credit" + ? allocateCredits(asset, event.validationStatus) + : []; + const packet = { + eventId: event.id, + assetId: asset.id, + assetTitle: asset.title, + assetType: asset.type, + generatedAt, + decision, + flags, + creditAllocations, + actions: buildActions(flags, event, asset), + }; + packet.reuseDigest = createReuseDigest(packet); + return packet; +} + +function allocateCredits(asset, validationStatus) { + const base = BASE_POINTS[validationStatus] || BASE_POINTS.unverified; + return (asset.maintainers || []).map((maintainer) => ({ + userId: maintainer.userId, + assetId: asset.id, + points: Math.round(base * maintainer.share), + reason: `${maintainer.role} reuse credit`, + })); +} + +function buildActions(flags, event, asset) { + if (flags.length === 0) { + return ["Publish reuse credit to curator profiles"]; + } + const actions = []; + if (flags.includes("MISSING_CITATION")) { + actions.push(`Add citation evidence for ${event.id}`); + } + if (flags.includes("LICENSE_MISMATCH")) { + actions.push(`Resolve ${event.licenseUsed} vs ${asset.license} license mismatch`); + } + if (flags.includes("SELF_REUSE")) { + actions.push("Require independent reuse evidence before awarding curator reputation"); + } + if (flags.includes("DUPLICATE_EVIDENCE")) { + actions.push(`Deduplicate evidence hash ${event.evidenceHash}`); + } + return actions; +} + +function buildReputationSummary(audit) { + const packets = audit.reusePackets || []; + const counts = { + reuseEvents: packets.length, + awarded: packets.filter((packet) => packet.decision === "award_credit").length, + quarantined: packets.filter((packet) => packet.decision === "quarantine_credit").length, + duplicateEvidence: countFlag(packets, "DUPLICATE_EVIDENCE"), + licenseIssues: countFlag(packets, "LICENSE_MISMATCH"), + }; + const pointsByUser = new Map(); + const badgesByUser = new Map(); + for (const packet of packets) { + for (const allocation of packet.creditAllocations) { + pointsByUser.set(allocation.userId, (pointsByUser.get(allocation.userId) || 0) + allocation.points); + if (!badgesByUser.has(allocation.userId)) { + badgesByUser.set(allocation.userId, new Set()); + } + badgesByUser.get(allocation.userId).add(`${packet.assetType}_reuse_${roleFromReason(allocation.reason)}`); + } + } + + const reputationDeltas = [...pointsByUser.keys()].sort().map((userId) => ({ + userId, + points: pointsByUser.get(userId), + badges: [...(badgesByUser.get(userId) || [])].sort(), + })); + + return { counts, reputationDeltas }; +} + +function createReuseDigest(packet) { + const stableFacts = { + eventId: packet.eventId, + assetId: packet.assetId, + decision: packet.decision, + flags: [...(packet.flags || [])].sort(), + creditAllocations: [...(packet.creditAllocations || [])].sort((left, right) => + `${left.userId}:${left.assetId}`.localeCompare(`${right.userId}:${right.assetId}`) + ), + }; + return `dcrcl_${crypto.createHash("sha256").update(JSON.stringify(stableFacts)).digest("hex").slice(0, 24)}`; +} + +function countFlag(packets, flag) { + return packets.filter((packet) => packet.flags.includes(flag)).length; +} + +function roleFromReason(reason) { + return String(reason).split(" ")[0]; +} + +module.exports = { + auditReuseCredits, + buildReputationSummary, + createReuseDigest, +}; diff --git a/dataset-curation-reuse-credit-ledger/requirements-map.md b/dataset-curation-reuse-credit-ledger/requirements-map.md new file mode 100644 index 0000000..7be75d6 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/requirements-map.md @@ -0,0 +1,15 @@ +# Requirements Map + +| Issue #15 requirement | Implementation coverage | +| --- | --- | +| Contributor credits | Allocates points to dataset curators, validators, and code maintainers. | +| Dataset uploads or curation | Focuses on downstream reuse of curated datasets and code assets. | +| Visible credit on researcher profiles | Emits profile-ready reputation deltas and badges. | +| Transparent reputation scoring | Uses deterministic point allocations from validation status and maintainer share. | +| Peer validation | Awards higher credit to replicated or validated reuse events. | +| Abuse-resistant scoring | Quarantines missing citations, license mismatches, self-reuse, and duplicate evidence. | +| Project timelines and auditability | Emits per-reuse packets with stable `dcrcl_` digests. | + +## Non-Overlap Statement + +This slice focuses on downstream reuse credit for curated datasets and code. It does not duplicate broad reputation ledgers, review safeguards, abuse/appeals modules, peer-review calibration, reviewer COI assignment, contributor credit attestation, transparency receipts, correction-impact ledgers, or mentorship impact ladders. diff --git a/dataset-curation-reuse-credit-ledger/sample-data.js b/dataset-curation-reuse-credit-ledger/sample-data.js new file mode 100644 index 0000000..d6782a6 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/sample-data.js @@ -0,0 +1,54 @@ +const assets = [ + { + id: "dataset-cell-42", + title: "Single-cell assay benchmark", + type: "dataset", + license: "CC-BY-4.0", + maintainers: [ + { userId: "u-alina", role: "curator", share: 0.7 }, + { userId: "u-minh", role: "validator", share: 0.3 }, + ], + }, + { + id: "code-qc-7", + title: "QC pipeline", + type: "code", + license: "MIT", + maintainers: [{ userId: "u-minh", role: "maintainer", share: 1 }], + }, +]; + +const reuseEvents = [ + { + id: "reuse-1", + assetId: "dataset-cell-42", + reuserId: "u-labwest", + projectId: "paper-9", + citation: "DOI:10.1000/cell42", + licenseUsed: "CC-BY-4.0", + evidenceHash: "sha256:evidence-1", + validationStatus: "replicated", + }, + { + id: "reuse-2", + assetId: "dataset-cell-42", + reuserId: "u-alina", + projectId: "paper-self", + citation: "", + licenseUsed: "CC0", + evidenceHash: "sha256:evidence-1", + validationStatus: "unverified", + }, + { + id: "reuse-3", + assetId: "code-qc-7", + reuserId: "u-labwest", + projectId: "paper-10", + citation: "GitHub release v1.4", + licenseUsed: "MIT", + evidenceHash: "sha256:evidence-3", + validationStatus: "validated", + }, +]; + +module.exports = { assets, reuseEvents }; diff --git a/dataset-curation-reuse-credit-ledger/test.js b/dataset-curation-reuse-credit-ledger/test.js new file mode 100644 index 0000000..840f0f7 --- /dev/null +++ b/dataset-curation-reuse-credit-ledger/test.js @@ -0,0 +1,123 @@ +const assert = require("assert"); + +const { + auditReuseCredits, + buildReputationSummary, + createReuseDigest, +} = require("./index"); + +const generatedAt = "2026-05-20T13:30:00.000Z"; + +const assets = [ + { + id: "dataset-cell-42", + title: "Single-cell assay benchmark", + type: "dataset", + license: "CC-BY-4.0", + maintainers: [ + { userId: "u-alina", role: "curator", share: 0.7 }, + { userId: "u-minh", role: "validator", share: 0.3 }, + ], + }, + { + id: "code-qc-7", + title: "QC pipeline", + type: "code", + license: "MIT", + maintainers: [{ userId: "u-minh", role: "maintainer", share: 1 }], + }, +]; + +const reuseEvents = [ + { + id: "reuse-1", + assetId: "dataset-cell-42", + reuserId: "u-labwest", + projectId: "paper-9", + citation: "DOI:10.1000/cell42", + licenseUsed: "CC-BY-4.0", + evidenceHash: "sha256:evidence-1", + validationStatus: "replicated", + }, + { + id: "reuse-2", + assetId: "dataset-cell-42", + reuserId: "u-alina", + projectId: "paper-self", + citation: "", + licenseUsed: "CC0", + evidenceHash: "sha256:evidence-1", + validationStatus: "unverified", + }, + { + id: "reuse-3", + assetId: "code-qc-7", + reuserId: "u-labwest", + projectId: "paper-10", + citation: "GitHub release v1.4", + licenseUsed: "MIT", + evidenceHash: "sha256:evidence-3", + validationStatus: "validated", + }, +]; + +function test(name, fn) { + try { + fn(); + console.log(`ok - ${name}`); + } catch (error) { + console.error(`not ok - ${name}`); + console.error(error); + process.exitCode = 1; + } +} + +test("allocates valid reuse credit and quarantines unsafe evidence", () => { + const audit = auditReuseCredits({ assets, reuseEvents, generatedAt }); + + const valid = audit.reusePackets.find((packet) => packet.eventId === "reuse-1"); + assert(valid, "expected reuse-1 packet"); + assert.strictEqual(valid.decision, "award_credit"); + assert.deepStrictEqual(valid.flags, []); + assert.deepStrictEqual(valid.creditAllocations, [ + { userId: "u-alina", assetId: "dataset-cell-42", points: 21, reason: "curator reuse credit" }, + { userId: "u-minh", assetId: "dataset-cell-42", points: 9, reason: "validator reuse credit" }, + ]); + + const unsafe = audit.reusePackets.find((packet) => packet.eventId === "reuse-2"); + assert(unsafe, "expected reuse-2 packet"); + assert.strictEqual(unsafe.decision, "quarantine_credit"); + assert(unsafe.flags.includes("MISSING_CITATION")); + assert(unsafe.flags.includes("LICENSE_MISMATCH")); + assert(unsafe.flags.includes("SELF_REUSE")); + assert(unsafe.flags.includes("DUPLICATE_EVIDENCE")); + assert(unsafe.actions.some((action) => action.includes("Add citation evidence"))); +}); + +test("builds profile-ready reputation summary", () => { + const audit = auditReuseCredits({ assets, reuseEvents, generatedAt }); + const summary = buildReputationSummary(audit); + + assert.deepStrictEqual(summary.counts, { + reuseEvents: 3, + awarded: 2, + quarantined: 1, + duplicateEvidence: 1, + licenseIssues: 1, + }); + assert.deepStrictEqual(summary.reputationDeltas, [ + { userId: "u-alina", points: 21, badges: ["dataset_reuse_curator"] }, + { userId: "u-minh", points: 24, badges: ["code_reuse_maintainer", "dataset_reuse_validator"] }, + ]); +}); + +test("creates stable reuse digests", () => { + const audit = auditReuseCredits({ assets, reuseEvents, generatedAt }); + const packet = audit.reusePackets.find((item) => item.eventId === "reuse-2"); + + const first = createReuseDigest(packet); + const second = createReuseDigest({ ...packet, actions: [...packet.actions] }); + + assert.strictEqual(first, second); + assert.match(first, /^dcrcl_[a-f0-9]{24}$/); +});