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

@tracescale/verifier

v0.3.0

Published

Apache-2.0 reference verifier for Tracescale norp v1.0 receipts. Strict, async, single-chain verification with structured AUD-01 reports and a terminal renderer.

Downloads

292

Readme

@tracescale/verifier

Apache-2.0 reference verifier for the norp v1.0 wire format. Strict, async, single-chain verification producing structured AUD-01 reports plus a terminal renderer. No @nonsudo/* dependency required.

Install

pnpm add @tracescale/verifier

Version posture

The current release is 0.3.0, a coordinated republish with @tracescale/[email protected] and @tracescale/[email protected] that widens the accepted canonical vocabulary; verification logic is unchanged from 0.2.1 (see CHANGELOG.md). The 0.x stream is published and externally consumable; a clean npm install performs end-to-end receipt verification from a fresh Node container.

The schema-pack registry public API (see "Schema-pack registry" below) ships since 0.2.1 and is semver-governed from that release forward. The registry is inert at 0.3.0: registered packs do not affect verification.

Supported algorithms

| Kind | Identifier (wire format) | Status at 0.3.0 | |---|---|---| | Signature | ed25519 | Registered; verification active | | Signature | ml-dsa-65 | Identifier shape supported in the registry; handler not yet registered | | Hash | sha256 | Registered |

The verifier reads signature.alg and signature_pq.alg from receipts and dispatches through the algorithm registry. Unknown algorithms produce a finding rather than a silent accept. getSupportedAlgorithms() returns the list of currently registered signature algorithms.

Verification modes

The verifier defaults to strict mode: every signature is cryptographically verified, the hash chain is checked, and the timestamp profile is evaluated. There is no permissive mode, no demo mode, no compatibility mode for older spec versions, and no multi-chain bundle mode.

One opt-in exception exists. A caller MAY enable integrity-only mode (skipSignatureVerification in the library, --skip-signature on the CLI), which checks chain integrity and schema but bypasses signature verification. Integrity-only mode is not cryptographic verification. It emits the signature_not_evaluated finding, sets checks.signature to SKIPPED, and resolves the chain to PARTIAL rather than VALID. A result produced in this mode must not be described as "verified". See the Integrity-only mode section below.

Integrity-only mode (skip signature verification)

Some callers need to check chain integrity and schema without access to signing public keys, for example when triaging an exported chain before keys are resolvable. For that case the verifier offers an opt-in integrity-only mode.

  • Library: pass skipSignatureVerification: true.
  • CLI: pass --skip-signature.

In this mode the per-receipt signature loop is bypassed. The verifier still checks the hash chain, timestamp profile, and schema. It emits a single chain-level signature_not_evaluated finding (severity: warning, payload { reason: "skipped_by_caller", mode: "integrity_only", signature_count }), sets checks.signature to SKIPPED, and resolves verification_result to PARTIAL when every other evaluated check passes.

Integrity-only mode is not cryptographic verification. A result produced in this mode must not be reported as "verified". The CLI prints an explicit banner to this effect.

Trust boundary

The verifier proves three things:

  1. Cryptographic linkage. Each receipt's signature verifies under the public key resolved by the caller's PublicKeyResolver.
  2. Structural integrity. Each receipt is well-formed per norp v1.0 schema, enums, compositions, emission rules, and hash formats.
  3. Chain integrity. Receipts link via prev_hash, sequence_number values are monotonic without gaps or duplicates, and chain_id is consistent.

The verifier does not prove:

  • That the resolved public key belongs to a legitimate emitter (key registry / trust anchor problem).
  • That the emitter was authorized to act on behalf of any user (mandate / delegation problem).
  • That the deployment running the proxy was trustworthy (deployment manifest / attestation problem).
  • That the policy that produced these decisions was correct (policy review problem).

PublicKeyResolver is the trust boundary. A caller supplying a resolver backed by a trusted key registry (for example, a signed deployment manifest's key list) establishes trust. A caller supplying a resolver that returns any public key without verification proves cryptographic linkage but not authorization.

Usage

import { verifyChain, renderAudMini } from "@tracescale/verifier";

const report = await verifyChain(receipts, {
  publicKeyResolver: async (key_id, alg) => {
    /* alg is the byte-identical string carried in receipt.signature.alg
     * (today: "ed25519"; future: "ml-dsa-65" when post-quantum lands).
     * Resolvers that do not differentiate by algorithm can accept
     * `_alg` and ignore it. */
    return registry.get(key_id, alg) ?? null;
  },
});

if (report.result === "INVALID") {
  process.stderr.write(renderAudMini(report));
  process.exit(1);
}

Migrating from 0.1.0

The PublicKeyResolver signature changed in 0.2.0 from (key_id, receipt) => ... to (key_id, alg) => .... Existing resolvers that ignored the second parameter need no functional change beyond renaming or accepting _alg. Resolvers that read the receipt body should move that logic into the caller and use the algorithm string instead.

NDJSON input

import { parseNdjsonReceipts, verifyChain } from "@tracescale/verifier";

const text = await readFileAsString(path); // caller's responsibility
const parsed = parseNdjsonReceipts(text);
for (const e of parsed.parse_errors) {
  console.warn(`line ${e.line_number}: ${e.message}`);
}
const report = await verifyChain([...parsed.receipts], { publicKeyResolver });

parseNdjsonReceipts is pure and synchronous and does not read files. File I/O belongs to CLI/app wrappers. It skips empty and #-comment lines, and inserts null placeholders for malformed JSON lines so that input_index references stay aligned with the original line numbering.

Optional report self-signing

import { signReport } from "@tracescale/verifier";

const signed = await signReport(report, verifierPrivateKey, "verifier-key-01");

The verifier reuses the same canonicalization and Ed25519 primitives as @tracescale/norp. report_hash is computed over the canonical body excluding report_hash and report_signature; report_signature is computed over the canonical body including report_hash but excluding report_signature.

Determinism

Same input + same options.now() value produces identical report bytes after JCS canonicalization. Inject now for reproducible reports:

const report = await verifyChain(receipts, {
  publicKeyResolver,
  now: () => "2026-05-22T10:35:12.000Z",
});

TSA verification

TsaVerifier is an interface with the implementation supplied by the caller. The default behavior when no TsaVerifier is supplied is tsa_status: NOT_CHECKED and an INFO-severity TSA_TOKEN_NOT_CHECKED finding for each receipt that carries TSA fields. The verifier does not flag receipts as INVALID simply because no TSA verifier was supplied.

Schema-pack registry

The package exports a schema-pack registry whose API is stable from 0.2.0 forward. Packs are inert at 0.3.0: registering a pack does not affect any verification result, finding, status field, or emitted report.

import {
  registerSchemaPack,
  listSchemaPacks,
  getSchemaPack,
  type SchemaPackManifest,
} from "@tracescale/verifier";

registerSchemaPack(manifest);
const registered = listSchemaPacks();
const one = getSchemaPack("example.namespace.v1");

The SchemaPackManifest interface admits two pack tiers:

  • Open extension packs declared by anyone, additive-only on receipt fields and named checks per the norp namespace-isolation rule.
  • Verified packs carrying a signed_manifest value. Validation of signed_manifest is not implemented at 0.3.0 and lands when the first verified pack ships.

The interface is semver-governed: future versions may add optional fields; renaming or removing existing fields requires a major version bump of @tracescale/verifier.

API

async function verifyChain(receipts: unknown[], options: VerifyOptions): Promise<VerifierReport>;
function parseNdjsonReceipts(input: string): ParseNdjsonResult;
function renderAudMini(report: VerifierReport): string;
async function signReport(report: VerifierReport, privateKey: Uint8Array, keyId: string): Promise<VerifierReport>;
async function verifyReportManifest(manifest: unknown, options: ReportManifestVerificationOptions): Promise<ReportManifestVerificationResult>;

// Crypto agility (0.2.0)
function getSupportedAlgorithms(): readonly string[];          // signature algorithms
function getSupportedHashAlgorithms(): readonly string[];      // hash algorithms
class AlgorithmRegistry { /* register / get / getSupportedAlgorithms */ }
class HashRegistry { /* register / get / getSupportedHashAlgorithms */ }

type PublicKeyResolver = (key_id: string, alg: string) => Uint8Array | null | Promise<Uint8Array | null>;

See src/types.ts and src/crypto/ for the full type surface.

Observe-mode disclosure

When a chain contains one or more observe-mode receipts (mode === "observe"), the verifier emits a structured observe_mode_disclosure field on the report:

"observe_mode_disclosure": {
  "counterfactual_count": 3,
  "would_have_block_rate": 0.3333333333333333,
  "would_have_step_up_rate": 0.3333333333333333,
  "actual_execution_rate": 1
}

Pure enforce-mode chains have no observe_mode_disclosure field. The disclosure is informational. Its presence does not by itself make a chain INVALID.

The verifier validates the observe-mode internal-consistency rules per receipt. A top-level decision that does not equal actual_execution_decision, or a counterfactual that is not true when actual differs from would-have, raises an ERROR finding with category OBSERVE_MODE_DECISION_INVARIANT_VIOLATED. Missing required observe-mode fields raise SCHEMA_VIOLATION findings. No new AUD-01 named check is added; only the one finding category.

See ../../../docs/spec/observe-mode.md for the full normative shape.

Signed report manifest verification

Separate from verifyChain (which verifies NORP receipt chains), the package exports verifyReportManifest for verifying signed RPT-01 report manifests produced by @tracescale/reports.signRptManifest and the @nonsudo/reports generators (Blocked Actions, Executive Summary, FINRA 2210, FINRA 3110).

import {
  verifyReportManifest,
  type ReportManifestVerificationResult,
} from "@tracescale/verifier";

const result = await verifyReportManifest(manifest, {
  expectedPublicKey: pubKey,
  sourceReceipts: receipts,         // optional
  verifySourceChain: true,          // requires sourceReceipts + sourceChainPublicKeyResolver
  sourceChainPublicKeyResolver: (kid, _alg) => keystore.get(kid),
});

Result envelope (flat: no overall_verdict or fully_verified field)

interface ReportManifestVerificationResult {
  manifest_valid: boolean;
  signature_valid: boolean;
  source_references_valid: boolean | "NOT_RUN";
  source_chain_verification: "VERIFIED" | "FAILED" | "NOT_RUN" | "INSUFFICIENT_DATA";
  evidence_context: "demo" | "test" | "production";
  report_type_code: string;
  source_receipt_count: number;
  findings: ReportVerificationFinding[];
}

source_receipt_count is locked to manifest.source_receipt_hashes.length. The verifier never reports sourceReceipts.length here. The field reflects what the signed manifest declares, not what the caller supplied.

Locked verification rule (consumer-side; lives in spec, not on result)

fully_verified =
  result.manifest_valid === true
  && result.signature_valid === true
  && result.source_references_valid === true
  && result.source_chain_verification === "VERIFIED"

Strict-equality is intentional. "NOT_RUN" is a first-class result state, distinct from true and from a failure. The rule's === true and === "VERIFIED" checks mechanically prevent "NOT_RUN" from silently aggregating into a "verified" verdict. A signature-only verification (source_references_valid: "NOT_RUN") is explicitly NOT fully verified, and fully_verified resolves to false. Rendering layers (the CLI renderer, dashboards, downstream consumers) choose how to surface partial state to humans; that derivation is a rendering concern, not a verifier concern.

fully_verified is not a field on the result envelope. Keeping the rule in the spec rather than in a derived field prevents the field from drifting away from the underlying flags.

Verification stages

  1. RPT-01 schema. Delegated to @tracescale/reports.validateUnsignedRptManifest. Failure short-circuits: subsequent checks return NOT_RUN/sentinel and a single REPORT_MANIFEST_SCHEMA_INVALID finding is emitted.
  2. Ed25519 signature. Delegated to @tracescale/reports.verifyRptManifestSignature. Public key is resolved from expectedPublicKey (preferred) or keyResolver. Failure emits REPORT_MANIFEST_SIGNATURE_INVALID.
  3. Source receipt hash references. Delegated to @tracescale/reports.verifySourceReceiptReferences when sourceReceipts is supplied. Reuses the three canonical codes REPORT_SOURCE_RECEIPT_MISSING, REPORT_SOURCE_RECEIPT_DUPLICATE, REPORT_SOURCE_RECEIPT_HASH_MALFORMED. When receipts are not supplied, source_references_valid is "NOT_RUN" and no reference-related findings are emitted.
  4. Evidence-context consistency. When source receipts are supplied and carry evidence_context, mismatches against manifest.evidence_context emit REPORT_EVIDENCE_CONTEXT_INCONSISTENT. This produces only a finding; it does not affect source_references_valid.
  5. Optional source chain AUD-01 verification. When sourceReceipts is supplied AND verifySourceChain === true, the existing verifyChain runs against the chain. Verdict mapping: VALID becomes "VERIFIED", INVALID becomes "FAILED". Without a sourceChainPublicKeyResolver, the result is "INSUFFICIENT_DATA". Chain-verify findings are merged into the result's findings array under their original category codes.

Finding codes (all severity ERROR)

| Code | Origin | Emitted when | |---|---|---| | REPORT_MANIFEST_SCHEMA_INVALID | local | RPT-01 schema validation fails | | REPORT_MANIFEST_SIGNATURE_INVALID | local | Signature does not verify, or no public key is resolvable for key_id | | REPORT_SOURCE_RECEIPT_MISSING | reused from @tracescale/reports | A declared hash does not resolve to a supplied receipt | | REPORT_SOURCE_RECEIPT_DUPLICATE | reused from @tracescale/reports | source_receipt_hashes carries duplicates (also caught at schema layer) | | REPORT_SOURCE_RECEIPT_HASH_MALFORMED | reused from @tracescale/reports | A declared hash is not sha256:<64-hex> (also caught at schema layer) | | REPORT_EVIDENCE_CONTEXT_INCONSISTENT | local | A source receipt's evidence_context differs from the manifest's |

Findings have severity ERROR because each code directly breaks the evidentiary meaning of the bundle. Reducing REPORT_EVIDENCE_CONTEXT_INCONSISTENT to a warning, for example, would let evidence_context: production manifests over demo receipts disappear in consumers that filter findings by severity.

Out of scope

  • CLI rendering of verifyReportManifest results lands in a follow-on slice.
  • Dashboard reports rendering is a separate workstream.
  • Modifications to verifyChain or AUD-01 named-check semantics.

What verifyReportManifest does NOT verify

  • That the resolved public key belongs to a legitimate issuer (same trust boundary as verifyChain).
  • Cross-chain provenance. The verifier operates on a single signed manifest plus at most one supplied source chain.
  • That the manifest's report_content body is correct with respect to the source chain. It verifies that hashes resolve, not that generator logic was sound.

License

Apache-2.0. See LICENSE.