@latimer-woods-tech/provenance
v0.1.0
Published
Chain-of-custody engine: DB-agnostic asset lineage resolver + dependency-free license certificate (SHA-256 hash + HMAC-SHA-256 signature). The moat, extracted.
Readme
@latimer-woods-tech/provenance
The chain-of-custody engine — the moat, extracted. Two dependency-free, Cloudflare-Workers-native primitives that any Factory app can reuse:
resolveLineage— a storage-agnostic asset lineage resolver. You inject two async I/O adapters; the package owns the whole graph algorithm (a hop-capped, cycle-safe, breadth-first upward walk with longest-path depth assignment) and returns a display-ordered chain of custody.buildCertificate/renderCertificateHtml— a verifiable license certificate: a SHA-256 content hash (tamper-evidence) plus an HMAC-SHA-256 platform signature (authenticity), with a self-contained printable HTML rendering (no PDF dependency).
No ORM, no PDF engine, no Node built-ins — safe inside a Worker.
Install
npm install @latimer-woods-tech/provenanceLineage
The package never talks to your database. You supply two adapters; it does the rest.
import { resolveLineage } from '@latimer-woods-tech/provenance';
const lineage = await resolveLineage({
targetAssetId,
// one BFS hop upward: parent edges of the given child ids
fetchParents: async (childIds) =>
(await db.select().from(assetLineage).where(inArray(assetLineage.childAssetId, childIds)))
.map((r) => ({ parent: r.parentAssetId, child: r.childAssetId, relation: r.relation })),
// metadata for the given asset ids (order-independent)
fetchAssets: async (ids) =>
(await db.select().from(assets).where(inArray(assets.id, ids)))
.map((a) => ({ asset_id: a.id, title: a.title, slug: a.slug, kind: a.kind, creator_id: a.creatorId })),
});
// → { target_asset_id, root_asset_ids, chain (root-first), depth, is_derived }The walk is defended against cycles (a visited set) and runaways (a maxHops cap,
default 64). A DAG — an asset merged from multiple sources — resolves to multiple
roots and longest-path depths, ordered deterministically.
Certificate
import { buildCertificate, renderCertificateHtml } from '@latimer-woods-tech/provenance';
const cert = await buildCertificate(input /* resolved license/order/asset/lineage */, {
secret: env.JWT_SECRET, // keys the HMAC signature
verifyUrl: 'https://…/verify', // where a holder re-verifies against live state
});
const html = renderCertificateHtml(cert, { brand: 'DJMEXXICO · Xico City' });buildCertificate folds the lineage chain into the signed canonical form, so
tampering with provenance breaks the hash. renderCertificateHtml produces inline,
script-free, print-to-PDF-ready HTML; brand sets the eyebrow line (the package
itself carries no app identity).
Guarantees
- Deterministic — same inputs, same
canonical/hash/signature. - Workers-safe —
crypto.subtle+TextEncoderonly; no Node built-ins, noBuffer. - Zero runtime dependencies.
License
MIT © Latimer Woods Tech
