npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zanii/custody-verdicts

v0.1.0

Published

The one custody verifier: computes the eight subject-page verdict rows (purpose, self-sovereign, nobody-read-it, never-trained-on, no-copy-kept, residency, subprocessor, minimization) from an offline-verified SubjectHistory. Pure, synchronous, no network.

Readme

@zanii/custody-verdicts

The one verifier. Zanii Custody's hard rule is one verifier, two surfaces: the subject page (zanii-id) and @zanii/cli import this same package and call the same function. Never two implementations of the verdict logic: two drift, and the drift always favours the vendor.

Verdicts are computed, never stored. computeVerdicts derives eight verdict rows at view time, in the reader's browser or the auditor's terminal, from the evidence itself: a SubjectHistory from @zanii/subject whose receipts were already verified offline (signature + delegation + scope), plus an optional evidence bag.

import { computeVerdicts, computeAnchorLag, TRAINING_MANIFEST_TAG } from '@zanii/custody-verdicts';

const rows = await computeVerdicts(history);             // history = await fetchSubjectHistory(tag)
const lag = computeAnchorLag(history.sth, new Date());   // seconds; null when unknown

TRAINING_MANIFEST_TAG ("zanii:training-manifests") is the well-known ledger tag under which declared training manifests are published. The page and the CLI fetch that slice to build the row-4 training evidence bag (ExtraEvidence.training).

computeVerdicts is async for one reason: rows 3 and 6 check claims through injected verifiers (@zanii/attest's quote verifier, @zanii/residency's region verifier) that may be async. Still pure: no network, no clock.

The eight rows

Always in this order; each { id, label, state, detail }, with detail the plain-language working the page renders verbatim:

| id | label | proven when | failed when | otherwise | |---|---|---|---|---| | purpose | Used only for its declared purpose | history verified AND every embedded payload is a well-formed custody touch (verifyTouch), retention shred (verifyKeyDestruction), or one of the rows 6-8 evidence payloads (structurally checked) | any offline check failed or any payload malformed | empty slice → cannot determine | | self_sovereign | You can check this yourself | every receipt verified offline AND the sth verifies | any offline check fails; bad sth | empty slice / no sth → cannot determine | | human_read | Nobody read it | a SIGNED coverage attestation (sig + trusted signer when pinned + attestationHash binding + enclave quote via injected verifier) proves all egress paths receipt-gated for the whole window | any receipt shows a human actor; the attestation is unsigned, badly signed, from an unrecognized signer, unbound, malformed, or its quote is disproven | not covered: "No coverage attestation for this window"; no quote verifier injected → cannot determine | | trained_on | Never used to train a model | @zanii/train's verifyAbsence passes for this subject's tag against a declared manifest anchored BEFORE the run attestation | the proof does not verify, names another ref, or the manifest is malformed | no declared run → not covered: "No declared training runs to check against"; wrong anchor order / missing times → cannot determine | | copy_kept | No copy was kept | every object touched in the slice has a shred receipt bound to its ref (payload ref = the touch's ref) that passes its own offline check AND verifyKeyDestruction, and any image evidence matches | a presented shred receipt fails either check; image evidence mismatches or is incomplete | none bound to a touched object's ref → not covered: "No key-destruction record on file."; some objects covered, some not → cannot determine | | residency | Data stayed in its declared region | EVERY custody touch has a verified in-set claim (a claim with a ref binds every touch of that object; a ref-less claim covers the touches at its own timestamp), every region signal authenticates (injected verifyRegion), and detectCrossings finds no exit | a verified claim's region is outside the allow-set (a border crossing), or a claim is malformed, invalid, or rides on an unverified receipt | no claims → not covered: "No residency attestations on record" (self-declared claims are not attestations and say so); empty slice, any touch without a claim, no jurisdiction config, or no injected verifyRegioncannot determine | | subprocessor | Only declared third parties touched it | every custody-touch junction and recorded transfer resolves to a declared party under a named agreement (verifyTransfers against the reader-pinned list, plus a junction/transfer divergence check) | any undeclared receiver, any transfer without an agreement id, any malformed record, or a junction naming a destination with no matching declared transfer | none recorded → not covered: "No onward transfers declared or recorded"; destinations on record but no reader-provided declaredSubprocessorscannot determine | | minimization | Only what was needed was taken | every recorded access proof verifies (verifyMinimization) against its committed minimum field set, and every commitment record recomputes to its recorded commitment (verifyFieldSet) | any excess access (a field outside the declared minimum), a widened-after-commitment set, or a malformed record | no commitments → not covered: "No minimum-field commitments on record"; empty slice, commitments without access proofs, access proofs without commitments, or an access proof against an unknown commitment → cannot determine |

Four states: proven, not covered, failed, cannot determine. The last three are as first-class as the first: a missing proof is not a clean result, and nothing here ever renders "never" out of a database column.

On an empty slice (zero receipts) rows 1-2 answer cannot determine and rows 3-5 stay grey regardless of evidence: there is nothing to cover. Row 3 with a coverage attestation over an empty slice answers cannot determine (coverage over nothing proves nothing about this subject's data); row 4 answers not covered; row 5's evidence lives in the slice, so an empty slice has none. Rows 6 and 8 answer cannot determine on an empty slice (there is no window to cover); row 7's evidence lives in the slice, so an empty slice answers not covered.

Evidence shapes

ExtraEvidence ({ coverage?, training?, destructionImage?, jurisdiction?, declaredSubprocessors? }) carries the evidence and reader-side trust roots that do not live in the slice; everything is checked offline, here, in this one place, against the real producers' shapes:

  • training (row 4) is { manifest, anchoredAt, runAttestedAt, absenceProof }: @zanii/train's TrainingManifest and AbsenceProof (sorted-Merkle over covering-gap pair leaves, reusing core's RFC 6962 inclusion proofs). The manifest anchor must precede the run attestation, or the ordering cannot be trusted.
  • destructionImage (row 5, optional) is { attestation, publishedImageHash }: a claimed destruction-image attestation plus the image hash the operator published. What is checked here is bag-internal consistency only, via @zanii/attest's attestationHash convention (the published image hash is attestationHash of the published attestation record): a mismatch is failed, a match appends the attested-image line to the proven detail. A match does NOT by itself prove the destruction ran under that image: nothing yet binds the shred receipt to the attestation, and the attestation's quote is not verified on this path. The full binding ships with the enclave phase. The destruction record itself needs no bag entry: it is the @zanii/retention shred receipt carried in the subject's slice (signature-covered embedded payload, target data.retention.shred), bound to the subject by slice membership and to the OBJECT by ref: a shred counts only for an object whose custody touches carry the same payload ref, and proven requires a verified bound shred for every object the slice touches (partial coverage answers cannot determine; a shred bound only to unrelated refs answers not covered). Its salted subject_commitment can later be proven to name the subject by revealing the salt.
  • coverage (row 3) is a signed coverage attestation ({ v: 1, type: 'coverage.attestation', allPathsGated, windowStart, windowEnd, imageHash, attestation, signer, sig, trustedSigners? }): every egress path receipt-gated (the @zanii/broker invariant) for a window, bound to an attested image hash, signed by the platform key. Verified fail-closed: signature and (when pinned) trusted signer, then the attestationHash binding, then verifyAttestation with the caller's injected quote verifier (pass it as computeVerdicts(history, evidence, { verifyQuote })). Without an injected verifier the quote stays unknown and the row can only reach cannot determine, never proven.
  • jurisdiction (row 6) is string[]: the declared jurisdiction as an allow-set of region codes (@zanii/residency's allowedRegions). Reader-side configuration, never producer-supplied (same trust rule as coverage.trustedSigners).
  • declaredSubprocessors (row 7) is string[]: the declared subprocessor list that onward transfers and custody junctions are checked against. Reader-side configuration, never producer-supplied.

Row 6: "Data stayed in its declared region"

Mechanism: residency claim payloads in the slice (@zanii/residency's buildResidencyClaim shape, embedded signature-covered exactly like the row-5 shred payloads) are checked with verifyResidency against the reader-pinned jurisdiction allow-set and the caller's injected verifyRegion (pass it as computeVerdicts(history, evidence, { verifyRegion })), which authenticates the region signal (a cloud provider identity document, a TEE geo signal) and returns the trusted region. detectCrossings then runs over the verified touch sequence: any exit from the allow-set is a border crossing and a red, never silent.

proven requires per-touch coverage, never a window span: EVERY custody touch in the slice must be covered by a verified in-set claim. A claim carrying a ref binds to every touch of that object; a ref-less claim is time-scoped and covers the touches made at its own timestamp. Every region signal must authenticate inside the allow-set, no claim may be malformed or unverified, and no crossings may be detected. A claim whose region source is self-declared (the operator asserted its own region) is not an attestation: it is not counted as evidence, and when every claim is self-declared the row answers not covered and says so.

The limit: the proof is only as strong as the injected geo trust root. A proven row means "signals your verifier trusts placed every touch inside your declared jurisdiction"; it cannot prove residency your geo source cannot. A touch with no claim answers cannot determine, never proven.

Row 7: "Only declared third parties touched it"

Mechanism: transfer records in the slice (@zanii/subprocessor's buildTransfer shape) are checked with verifyTransfers against the reader-pinned declaredSubprocessors list, and every custody-touch junction (@zanii/data-custody's junction field) must name a destination with a matching declared transfer: a divergence between the custody trail and the transfer record is a red, never silent.

proven requires: every recorded transfer's receiver is declared, every transfer carries an agreement id (stricter than the producer, which requires an id only for dpa/scc), no record is malformed, and every junction resolves to a declared party under a named agreement.

The limit: this proves the transfers that were recorded. An off-record leak (a processor that received plaintext and emitted no transfer receipt) cannot be caught here; that residual is the enclave problem, unchanged. And an agreement id is a reference, not a guarantee the counterparty honored it. Senders are not checked: verifyTransfers runs without a controller, so an undeclared from is indistinguishable from the origin and goes unchecked.

Row 8: "Only what was needed was taken"

Mechanism: field-set commitment records in the slice (@zanii/minimization's commitFieldSet output carried as a minimization.commit payload) are recomputed with verifyFieldSet (a mismatch means the declared minimum was widened after the commitment was recorded), and every access proof (buildAccessProof shape) is checked with verifyMinimization against its committed field set: any accessed field outside the declared minimum is a flagged excess, a red.

proven requires: every commitment record recomputes to its recorded commitment and every recorded access stayed inside its committed minimum. Commitments without access proofs, access proofs without commitments, or an access proof whose commitment is not on record answer cannot determine: an unchecked access is unknown, not clean.

The limit: this proves declared-vs-accessed: that the agent stayed inside the minimum that was committed. It does not prove the minimum was well-chosen (a judgement, not a cryptographic fact), and it proves the recorded accesses: an off-record read is the enclave problem, unchanged.

Trust roots: who may attest coverage

The same reader-side rule now guards three trust roots: coverage.trustedSigners (row 3), jurisdiction (row 6), and declaredSubprocessors (row 7). All three are reader-side configuration, merged into the evidence by the subject page or the CLI from its OWN deployment config, never from producer-supplied data: an attacker who could name their own signer, their own jurisdiction, or their own subprocessor list would attest anything.

A coverage attestation is only as good as the key that signs it. The producer side (enclave phase) signs with the platform's coverage-signing key; the reader side, the subject page and the CLI, pins the platform's coverage-signing DID(s) and merges them into the evidence as coverage.trustedSigners before calling computeVerdicts. Two rules make this a real trust root rather than a formality:

  • The list is reader-side configuration. It must never come from producer-supplied data: an attacker who could name their own key trusted would attest anything. For the same reason trustedSigners is NOT part of the signed record; it is excluded from the signature preimage and merged in after signing.
  • An unknown signer is never proven. When trustedSigners is present, a coverage attestation signed by any other DID is failed ("coverage attestation from an unrecognized signer"); an empty list trusts no one. When it is absent (day one), any well-formed did:key signer authenticates, and the remaining checks decide. Deployments should always pin once the enclave phase ships.

Row 3's producer side ships with the enclave phase. @zanii/attest + @zanii/broker produce signed coverage attestations there. Until then the evidence does not exist, the row answers not covered, and proven is unreachable: that is the design, not a gap. A "nobody read it" claim without hardware attestation would be our word, not evidence.

Guarantees

  • Pure: no network, no clock (computeAnchorLag takes now from the caller). Async only for the injected verifiers (enclave quote, region signal).
  • Never throws: malformed input yields honest cannot determine / not covered / failed rows with reasons (verification paths return results; creation paths throw).
  • No verdict is ever stored or served; the ledger serves raw evidence only.