@tracescale/reports
v0.1.0
Published
RPT-01 signed report manifest substrate — generic JCS-canonicalized, Ed25519-signed evidence-bundle manifests that reference NORP receipts by hash
Readme
@tracescale/reports
RPT-01 signed report manifest substrate.
@tracescale/reports provides the generic, signed evidence-bundle
manifest format that report generators use to wrap a body of report
content together with cryptographic references to the NORP receipts that
form its evidence. It is part of the open tracescale standard.
What RPT-01 is
- A JCS-canonicalized (RFC 8785), Ed25519-signed manifest object.
- A wrapper that names a body of
report_contentand lists thesource_receipt_hashesof the NORP receipts that back it. - The substrate on which higher-level report generators (FINRA 2210, FINRA 3110, blocked-actions, executive-summary) are built.
What RPT-01 is not
- Not a PDF. Not an HTML page. Not a dashboard view. Not a CSV/SQL export.
- Not a report generator. Generators live in
@nonsudo/*packages (DBM-07B). - Not a verifier integration. AUD-01 wiring lands in DBM-07C.
- Not a dashboard rendering layer (DBM-07D).
- Not a place to mutate NORP receipt semantics. Reports consume receipts; they do not modify them.
Signed manifest vs. rendered output
A signed RPT-01 manifest is a structural artefact: a deterministic JSON object that can be re-verified at any time without trusting the renderer that displayed it to a human. A PDF, HTML, or dashboard view is a rendering of the manifest, not the manifest itself. The manifest is the thing that is signed.
evidence_context requirement
Every RPT-01 manifest must carry evidence_context explicitly. Valid
values:
"demo"— produced from the demo scenario chains. Not evidence of real-world activity."test"— produced from automated test fixtures."production"— produced from a live runtime.
There is no silent default to "production". Construction fails if
evidence_context is omitted.
Whether a separate evidence_disclaimer field belongs on the signed
manifest is deferred to the v8.0 master feature list; see
DBM07A_FOLLOWUPS.md. In this slice, demo-warning surfacing is a
rendering-layer concern.
Source receipt hash references
source_receipt_hashes is a non-empty array of sha256:<64 hex> strings.
Each entry must match the canonical NORP receipt-hash format (the same
format @tracescale/norp enforces via isValidSha256Prefixed).
- Strict format:
/^sha256:[0-9a-f]{64}$/. - Empty strings are rejected.
- Duplicates are rejected at schema validation time.
- No silent normalization. What the caller passes is what the manifest carries and signs.
The helper verifySourceReceiptReferences checks that every listed hash
resolves to a receipt in a caller-provided receipt list. It does not
verify receipt signatures or chain validity — that is the verifier's
job.
Signing and verification model
- Canonicalize the unsigned manifest with JCS (RFC 8785).
- Sign the canonical bytes with Ed25519, producing a
Signatureobject of shape{ alg: "Ed25519", key_id, sig }wheresigis the base64-encoded 64-byte signature. The shape is reused verbatim from@tracescale/norpto avoid drift. - Attach
signatureto the manifest. Thesignaturefield is never part of the signing input. - To verify, strip
signature, re-canonicalize the remainder, and Ed25519-verify the stored signature against the provided public key.
Example — unsigned manifest
import type { UnsignedRptManifest } from "@tracescale/reports";
const manifest: UnsignedRptManifest = {
manifest_version: "rpt-01/v0.1",
manifest_id: "rpt-2026-05-16-001",
manifest_type: "content_report",
report_type_code: "EXAMPLE_REPORT",
generated_at: "2026-05-16T00:00:00.000Z",
generator_id: "example-report-generator",
evidence_context: "demo",
source_chain_id: "leadingresponse-may22-main",
source_receipt_hashes: [
"sha256:57508215b7a676f0f43b4f60ba25c4861c0965d1e4cf11dc50374f154d555480",
],
report_content: {
summary: "example",
items: [],
},
};Example — sign
import { signRptManifest } from "@tracescale/reports";
const signed = signRptManifest({
manifest,
privateKey, // 32-byte Uint8Array or hex string
key_id: "example-key-01",
});Example — verify
import { verifyRptManifestSignature } from "@tracescale/reports";
const result = verifyRptManifestSignature({
manifest: signed,
publicKey, // 32-byte Uint8Array or hex string
});
if (!result.valid) {
throw new Error(`manifest signature invalid: ${result.reason}`);
}Example — verify source receipt references
import { verifySourceReceiptReferences } from "@tracescale/reports";
const refs = verifySourceReceiptReferences({
manifest: signed,
receipts: chainReceipts, // each carries receipt_hash (or hash)
});
for (const f of refs.findings) {
console.error(`[${f.code}] ${f.message}`);
}Boundaries
@tracescale/reportsdepends on@tracescale/norponly.- It does not depend on
@tracescale/verifier,@tracescale/tsa-sidecar, or any@nonsudo/*package. - No reverse imports:
@tracescale/norp,@tracescale/verifier, and@tracescale/tsa-sidecarmust not import from@tracescale/reports.
Test vectors
Deterministic test vectors live under test-vectors/. They are
regenerated via node --experimental-strip-types test-vectors/generate.ts
(run from the package root after a build). Tests in test/vectors.test.ts
load the committed JSON directly.
The vectors use a hardcoded test seed (_meta.json.seed_hex). Test seed
only — never use it for real signing.
License
Apache-2.0. See LICENSE.
