From 82b6d9896afa108adc8fa1ced816359c71db84d5 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Wed, 11 Mar 2026 21:57:17 -0500 Subject: [PATCH 01/25] docs: add claims boundary and strengthen public messaging alignment --- README.md | 327 ++++++++++++------------ wiki/API-Overview.md | 142 ++++++++++ wiki/Claims-Boundary.md | 36 +++ wiki/Evidence-Integrity-Architecture.md | 118 +++++++++ wiki/FAQ.md | 56 ++++ wiki/Home.md | 66 +++++ wiki/Quick-Verification-Example.md | 166 ++++++++++++ wiki/SDK-Usage.md | 100 ++++++++ wiki/Security-Model.md | 80 ++++++ wiki/Threat-Model.md | 164 ++++++++++++ wiki/Vanta-Integration-Example.md | 62 +++++ wiki/Verification-Receipts.md | 94 +++++++ wiki/What-is-TrustSignal.md | 65 +++++ 13 files changed, 1307 insertions(+), 169 deletions(-) create mode 100644 wiki/API-Overview.md create mode 100644 wiki/Claims-Boundary.md create mode 100644 wiki/Evidence-Integrity-Architecture.md create mode 100644 wiki/FAQ.md create mode 100644 wiki/Home.md create mode 100644 wiki/Quick-Verification-Example.md create mode 100644 wiki/SDK-Usage.md create mode 100644 wiki/Security-Model.md create mode 100644 wiki/Threat-Model.md create mode 100644 wiki/Vanta-Integration-Example.md create mode 100644 wiki/Verification-Receipts.md create mode 100644 wiki/What-is-TrustSignal.md diff --git a/README.md b/README.md index b427404..535c50b 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,227 @@ # TrustSignal -Universal verification engine with a DeedShield property-record module and a forward path to healthcare credentialing. +[![CI](https://img.shields.io/github/actions/workflow/status/trustsignal-dev/trustsignal/ci.yml?branch=master&label=CI)](https://github.com/trustsignal-dev/trustsignal/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/license-proprietary-lightgrey)](LICENSE) +[![TypeScript](https://img.shields.io/badge/TypeScript-supported-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Coverage](https://img.shields.io/badge/coverage-threshold%2090%25-0A7F5A)](vitest.config.ts) +[![Security Checklist](https://img.shields.io/badge/security-checklist-informational)](SECURITY_CHECKLIST.md) +[![Threat Model](https://img.shields.io/badge/threat_model-documented-informational)](security/threat_model.md) +[![Audit Report](https://img.shields.io/badge/audit_report-available-informational)](security/audit_report.md) -## Release Status (Session 7 Final) +Website: https://trustsignal.dev -- Fastify v5 verification API contract: `/v1/verify-bundle`, `/v1/revoke`, `/v1/status/:bundleId` -- Halo2 circuits (non-membership + revocation): `gate_count=1024`, `k=10` -- ZKML artifact: `ml/zkml/deed_cnn.compiled` + benchmark (`proof_gen_ms=1506.46`, `auc=0.998`) -- JavaScript SDK (`sdk/`): `verify()`, `revoke()`, `status()` with ESM + CJS builds and zero runtime dependencies -- Test and quality posture: 64/64 tests passing, strict TypeScript clean, scoped coverage `99.34%` -- Security posture: OWASP audit + threat model published, JWT rotation support, rate limiting, structured log redaction -- CI posture: GitHub Actions jobs for lint, typecheck, tests+coverage, and Rust build/tests +TrustSignal provides signed receipts and verifiable provenance for compliance artifacts. -## Repository Scope +TrustSignal is an integrity layer, not a workflow replacement. It fits into existing compliance workflows without replacing the upstream system of record. -This repository is the main TrustSignal project. It contains: +## Why TrustSignal Exists -- Product-facing docs and governance artifacts under `docs/` -- TrustSignal verification runtime under `src/` -- DeedShield API/Web implementation in `apps/` -- Shared verification logic and contract code in `packages/` -- Halo2 and ZKML proof artifacts in `circuits/` and `ml/` +Many teams can show that a file was uploaded, reviewed, or approved. Fewer can later prove that the artifact under review is unchanged from the one originally collected. -## Quickstart +TrustSignal addresses that gap by issuing signed receipts and retaining verification state that make later drift detectable and auditable. The product is designed to sit behind an existing intake, evidence, or compliance platform, so the workflow owner keeps control of collection while TrustSignal supplies integrity evidence. -- All `/api/v1/*` endpoints except `/api/v1/health` require `x-api-key`. -- Configure API keys with `API_KEYS` and optional `API_KEY_SCOPES`. -- CORS is deny-by-default in production unless `CORS_ALLOWLIST` is set. -- In production, startup fails if `NOTARY_API_KEY`, `PROPERTY_API_KEY`, or `TRUST_REGISTRY_SOURCE` are missing. -- In production, startup also fails if `TRUSTSIGNAL_RECEIPT_SIGNING_PRIVATE_JWK`, `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWK`, or `TRUSTSIGNAL_RECEIPT_SIGNING_KID` are missing. -- Receipt and Vanta responses expose `anchor.subjectDigest` / `anchorSubjectDigest` plus `anchorSubjectVersion` so proof provenance can be audited independently of the raw receipt JSON. -- Receipt responses now include additive `receiptSignature` metadata (`signature`, `alg`, `kid`) when the receipt is issuer-signed. -- Revocation requires issuer signature headers: - - `x-issuer-id` - - `x-signature-timestamp` - - `x-issuer-signature` (signature over `revoke::`) +## Evidence Integrity Architecture -## Data Minimization Defaults +```mermaid +flowchart LR + A[Evidence Sources] --> B[Compliance Platform] + B --> C[TrustSignal] + C --> D[Signed Receipt] + D --> E[Verifiable Audit Evidence] +``` -- Receipts persist `inputsCommitment` and `rawInputsHash` (commitment hash), not full raw input payloads. +## Key Capabilities -## Local Demo +- signed receipt issuance and later receipt verification +- tamper-evident digest comparison and receipt reconstruction +- receipt lifecycle operations for retrieval, revocation, status, and anchoring +- versioned API surfaces for workflow integrations and partner-facing evidence payloads +- registry screening and normalized evidence outcomes for configured sources +- scoped authentication, request validation, rate limiting, and structured logging -### Terminal demo for partner conversations +DeedShield is the current application surface in this repository. The broader product framing is evidence integrity infrastructure for compliance artifacts. -Use the terminal-first Vanta demo when you need to show TrustSignal as backend evidence-integrity infrastructure rather than a UI product. +## Example Use Cases -```bash -npm run demo:vanta-terminal -``` +- issuing signed receipts for compliance artifacts at the point of collection +- re-verifying stored evidence before audit, review, or partner submission +- attaching verifiable provenance to partner-facing evidence payloads +- adding integrity checks to deed and property-record verification workflows without replacing the originating platform -What it shows: +## Quickstart / 5-Minute Demo -- baseline artifact intake -- signed receipt issuance -- persisted receipt verification -- tampered artifact intake using the same declared hash with changed bytes -- the recorded mismatch between declared hash and observed document digest +The lowest-risk local path is to run the API and web workspaces and try the product through the existing application surface. -This demo is intentionally conservative: +Prerequisites: -- it presents receipt signing and receipt verification as real -- it presents the evidence chain as tamper-evident -- it does not claim production-grade blockchain or ZK enforcement -- it uses dev-only proof status exactly as returned by the API today +- Node.js `>= 18` +- npm `>= 9` +- PostgreSQL `>= 14` for `apps/api` -### 2) Configure environment +Quickstart: ```bash +npm install cp .env.example .env.local +cp apps/api/.env.example apps/api/.env +npm -w apps/api run db:generate +npm -w apps/api run db:push +npm -w apps/api run dev ``` -Set real values in `.env.local` for: +Before running `db:push`, set at minimum a valid `DATABASE_URL` in `apps/api/.env`. The API template also includes scoped API-key settings and provider variables for the integration paths you want to exercise. -- `TRUSTSIGNAL_JWT_SECRETS` (or `TRUSTSIGNAL_JWT_SECRET`) -- `TRUSTSIGNAL_ZKP_BACKEND` -- `TRUSTSIGNAL_ZKP_PROVER_BIN` and `TRUSTSIGNAL_ZKP_VERIFIER_BIN` when `TRUSTSIGNAL_ZKP_BACKEND=external` - - Current bootstrap prover binary: `circuits/non_mem_gadget/target/release/zkp_service` -- `TRUSTSIGNAL_RECEIPT_SIGNING_PRIVATE_JWK` -- `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWK` -- `TRUSTSIGNAL_RECEIPT_SIGNING_KID` -- optional `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWKS` for multi-key verification by `kid` -- `POLYGON_MUMBAI_RPC_URL` -- `POLYGON_MUMBAI_PRIVATE_KEY` -- `DATABASE_URL` (or `SUPABASE_DB_URL` / `SUPABASE_POOLER_URL` / `SUPABASE_DIRECT_URL`; or set `SUPABASE_DB_PASSWORD` and use Supabase CLI pooler metadata) +In a second terminal: -Never commit real secrets. +```bash +npm -w apps/web run dev +``` -ZKP status note: +Default local ports: -- `dev-only` remains the default local mode. -- `external` now supports a real Halo2 proof round-trip through `circuits/non_mem_gadget/src/bin/zkp_service.rs`, but that binary currently proves a bootstrap attestation circuit over public proof inputs, not the final document-hash statement. -- Do not describe the current bootstrap circuit as full document authenticity or PII-preserving document hashing. +- web app: `http://localhost:3000` +- API: `http://localhost:3001` -Contract note: +What this local path proves: -- `packages/contracts` uses Hardhat 3 and needs Node 22+ for local compile/smoke runs. +- TrustSignal fits behind an existing workflow rather than replacing it +- verification requests produce signed receipts and stored verification state +- receipts can be retrieved and re-verified through the API surface -### 3) Run validation gates +For fuller local setup details, including PostgreSQL configuration and workspace-specific environment guidance, see `apps/api/SETUP.md`. For partnership demo materials, see `docs/partnership/vanta-2026-03-06/README.md`. -```bash -npm run lint -npm run typecheck -npm test -``` +## API Overview -### 3a) Run the signed-receipt DB smoke harness +TrustSignal exposes two main API surfaces in this repository. -This boots a temporary local PostgreSQL instance, points the API integration test at it, validates signed receipt issuance and verification, validates legacy unsigned receipt behavior, and then tears the database down. +Core TrustSignal `/v1/*` routes use JWT authentication: -```bash -npm run smoke:signed-receipt -``` +- `POST /v1/verify-bundle` +- `POST /v1/revoke` +- `GET /v1/status/:bundleId` -Local requirements for this harness: +Integration-oriented `/api/v1/*` routes use scoped `x-api-key` access: -- `initdb` -- `pg_ctl` -- `createdb` -- `psql` +- service and status: `GET /api/v1/health`, `GET /api/v1/status`, `GET /api/v1/metrics` +- verification: `POST /api/v1/verify`, `POST /api/v1/verify/attom`, `GET /api/v1/synthetic` +- receipt lifecycle: `GET /api/v1/receipt/:receiptId`, `GET /api/v1/receipt/:receiptId/pdf`, `POST /api/v1/receipt/:receiptId/verify`, `POST /api/v1/receipt/:receiptId/revoke`, `POST /api/v1/anchor/:receiptId`, `GET /api/v1/receipts` +- partner integrations: `GET /api/v1/integrations/vanta/schema`, `GET /api/v1/integrations/vanta/verification/:receiptId` +- registry services: `GET /api/v1/registry/sources`, `POST /api/v1/registry/verify`, `POST /api/v1/registry/verify-batch`, `GET /api/v1/registry/jobs`, `GET /api/v1/registry/jobs/:jobId` -Optional overrides: +`POST /api/v1/receipt/:receiptId/revoke` also expects signed issuer headers: `x-issuer-id`, `x-signature-timestamp`, and `x-issuer-signature`. -- `TRUSTSIGNAL_SMOKE_PG_PORT` -- `TRUSTSIGNAL_SMOKE_DB_USER` -- `TRUSTSIGNAL_SMOKE_DB_NAME` +The integration model is intentionally narrow: upstream platforms retain workflow ownership, while TrustSignal provides receipt, verification, status, and evidence services at the boundary. -### 4) Run DeedShield API/Web (workspace apps) +## Security Posture -```bash -npm -w apps/api run db:generate -npm -w apps/api run db:push -npm -w apps/api run dev -``` +This repository is operated with a security-first posture and explicit claim boundaries. -In a second terminal: +Implemented controls include: -```bash -npm -w apps/web run dev -``` +- API authentication for both JWT (`/v1/*`) and scoped API-key (`/api/v1/*`) surfaces +- signed receipts returned with verification results +- receipt lifecycle validation for retrieval, verification, status, and revocation state +- revocation controls with issuer authorization requirements +- rate limiting and abuse protection controls +- fail-closed dependency handling on critical verification and startup guardrail paths +- input validation at API boundaries and structured log redaction for selected sensitive fields +- database TLS enforcement checks for production API startup +- primary-source registry guardrails with explicit compliance-gap outcomes -## TrustSignal API Contract (`src/routes`) +Environment and auth requirements are real and enforced. Important variables include: -All TrustSignal `/v1/*` endpoints require `Authorization: Bearer `. +- `API_KEYS` and `API_KEY_SCOPES` +- `TRUSTSIGNAL_JWT_SECRETS` or `TRUSTSIGNAL_JWT_SECRET` +- `TRUSTSIGNAL_RECEIPT_SIGNING_PRIVATE_JWK` +- `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWK` +- `TRUSTSIGNAL_RECEIPT_SIGNING_KID` +- optional `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWKS` +- `DATABASE_URL` +- `NOTARY_API_KEY`, `PROPERTY_API_KEY`, and `TRUST_REGISTRY_SOURCE` for production verifier configuration +- `TRUSTSIGNAL_ZKP_BACKEND`, `TRUSTSIGNAL_ZKP_PROVER_BIN`, and `TRUSTSIGNAL_ZKP_VERIFIER_BIN` when using external proof infrastructure -- `POST /v1/verify-bundle` - - Validates request with Zod. - - Runs non-membership proof, revocation proof, and ZKML verification. - - Persists result to `VerificationRecord`. -- `POST /v1/revoke` - - Requires admin JWT claim (`role=admin` or equivalent claim form). - - Anchors nullifier on Polygon Mumbai and marks record revoked. -- `GET /v1/status/:bundleId` - - Returns latest persisted verification state for a bundle hash. -- `GET /api/v1/integrations/vanta/schema` - - Returns JSON Schema for Vanta-ingestable verification payloads. -- `GET /api/v1/integrations/vanta/verification/:receiptId` - - Returns structured verification evidence payload (`trustsignal.vanta.verification_result.v1`). -- `GET /api/v1/registry/sources` - - Returns configured primary-source registry adapters (OFAC/OIG/SAM/UK/BIS/CSL/NPPES/SEC/FDIC), freshness metadata, and circuit mapping. -- `POST /api/v1/registry/verify` - - Runs a source-specific check with cached results and returns normalized match evidence (`MATCH`, `NO_MATCH`, `COMPLIANCE_GAP`). -- `POST /api/v1/registry/verify-batch` - - Screens one subject across multiple registry sources and returns an aggregate summary including `complianceGapSources`. -- `GET /api/v1/registry/jobs` and `GET /api/v1/registry/jobs/:jobId` - - Exposes ZK oracle dispatch job state for registry checks (`QUEUED`, `DISPATCHED`, `SKIPPED`, `FAILED`). +Never commit real secrets, API keys, private keys, or local env files. Infrastructure claims such as encrypted-at-rest storage, TLS termination, and key custody require environment-specific evidence outside this repo. -Reference implementation: `tests/api/routes.test.ts`. +See `SECURITY_CHECKLIST.md`, `security/audit_report.md`, and `security/threat_model.md` for current evidence and open gaps. -## Security Defaults +## Repository Structure -- Input validation at API boundaries (Zod) -- JWT verification with key rotation (`TRUSTSIGNAL_JWT_SECRETS`) -- Rate limiting using `@fastify/rate-limit` -- Structured request logging with authorization redaction -- Fail-closed behavior on proof verification errors -- Production requires an explicit external ZKP backend; the built-in dev attestation path is blocked when `NODE_ENV=production` -- No stack traces or raw internals in API responses -- Primary-source registry guardrails with explicit `COMPLIANCE_GAP` outcomes when authoritative endpoints are unavailable or non-compliant +- `apps/api/`: DeedShield API and integration-facing routes +- `apps/web/`: DeedShield web app +- `src/`: TrustSignal runtime and `/v1/*` API surface +- `packages/core/`: shared receipt, integrity, and verification logic +- `packages/contracts/`: anchoring contract code and related tooling +- `sdk/`: JavaScript SDK for TrustSignal APIs +- `docs/`: canonical product, architecture, operations, and partnership documentation +- `security/`: audit and threat-model artifacts +- `tests/`: API, integration, middleware, and end-to-end coverage +- `circuits/` and `ml/`: supporting implementation and R&D artifacts, not the primary product interface -Detailed reports: +## Development Workflow -- `security/audit_report.md` -- `security/threat_model.md` +Primary validation commands: -## Data Model +```bash +npm run lint +npm run typecheck +npm test +``` -Primary runtime persistence model: +Full validation: -- Prisma `VerificationRecord` (`prisma/schema.prisma`) - - Bundle hash, proof outcomes, fraud score, proof latency - - Revocation state, reason, transaction hash, and revocation timestamp +```bash +npm run validate +``` -## SDK +Signed-receipt smoke validation: -The TrustSignal JavaScript SDK is under `sdk/` and exposes: +```bash +npm run smoke:signed-receipt +``` -- `verify(bundle)` -- `revoke(bundleHash, reason)` -- `status(bundleId)` +Development notes: -See `sdk/README.md` for usage examples. +- `apps/api/.env.example` is the main local template for API work +- `.env.example` at the repo root supports repo-level scripts and local placeholders +- contract-focused work in `packages/contracts` currently uses Hardhat 3 tooling and should be validated on Node 22+ +- when behavior or posture changes, update the relevant docs and checklists in the same change -## CI/CD +## Documentation + +Start with: + +- `docs/README.md` +- `docs/CANONICAL_MESSAGING.md` +- `PROJECT_PLAN.md` +- `SECURITY_CHECKLIST.md` +- `apps/api/SETUP.md` +- `sdk/README.md` +- `TASKS.md` +- `CHANGELOG.md` -GitHub Actions workflow: `.github/workflows/ci.yml` +Canonical architecture, API, and operations guidance also lives under `docs/final/`, including: -- `lint` -- `typecheck` -- `test` (with coverage) -- `rust-build` (Halo2 crate build + tests) +- `docs/final/02_ARCHITECTURE_AND_BOUNDARIES.md` +- `docs/final/03_SECURITY_AND_COMPLIANCE_BASELINE.md` +- `docs/final/05_API_AND_INTEGRATION_GUIDE.md` +- `docs/final/06_PILOT_AND_MARKETPLACE_READINESS.md` +- `docs/final/14_VANTA_INTEGRATION_USE_CASE.md` -## Vercel Deployment +## Claims / Compliance Boundaries -- API serverless entrypoint: `apps/api/api/[...path].ts` -- Root deployment policy config: `vercel.json` -- API-specific Vercel config (if deploying `apps/api` as project root): `apps/api/vercel.json` -- Root `vercel.json` currently rewrites `/api/*` traffic to the API serverless entrypoint. +TrustSignal provides technical verification signals, not legal determinations. -For production, deploy with environment variables managed in Vercel project settings (never in repo files). +TrustSignal should not currently be described as: -## Canonical Documentation +- a replacement for compliance, audit, or workflow systems +- a finished production-grade document-authenticity proof platform across all surfaces +- a completed compliance certification or a substitute for independent control validation +- a system that should log, expose, or anchor raw PII without explicit need and supporting controls -- `docs/README.md` -- `docs/final/01_EXECUTIVE_SUMMARY.md` -- `docs/final/11_NSF_GRANT_WHITEPAPER.md` -- `docs/final/12_R_AND_D_LOG.md` -- `docs/final/13_SOC2_READINESS_KICKOFF.md` -- `docs/final/14_VANTA_INTEGRATION_USE_CASE.md` -- `TASKS.md` -- `CHANGELOG.md` +Public messaging must not imply: -## Compliance and Claims Boundaries +- completed production readiness without infrastructure evidence +- final cryptographic or proof guarantees where the implementation is still partial or environment-gated +- compliance certifications that are not independently validated -- TrustSignal provides technical verification signals, not legal determinations. -- Avoid PII in logs and artifacts. -- Do not represent HIPAA or equivalent compliance unless infra and controls are independently validated. +This repository includes roadmap and supporting implementation material. Those elements must remain clearly separated from current implemented behavior in any public-facing material. diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md new file mode 100644 index 0000000..0ffe458 --- /dev/null +++ b/wiki/API-Overview.md @@ -0,0 +1,142 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# API Overview + +TrustSignal currently exposes two API surfaces in this repository. + +## API Families + +### Integration-Facing API + +The `/api/v1/*` surface is the main partner-facing integration API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. + +### Legacy JWT API + +The `/v1/*` surface is still present and is the surface used by the current JavaScript SDK. It uses bearer JWT authentication. + +## Request Path From the Codebase + +For the integration-facing API, the dominant request pattern is: + +```mermaid +sequenceDiagram + participant C as Client + participant G as apps/api/src/server.ts + participant E as VerificationEngine + participant V as localVerificationEngine + + C->>G: HTTP request + G->>G: Auth + validation + rate limit + G->>E: Engine interface call + E->>V: Execute operation + V-->>G: Typed result + G-->>C: Shaped API response +``` + +## `/api/v1/*` Endpoints + +| Method | Path | Auth | Purpose | +| --- | --- | --- | --- | +| `GET` | `/api/v1/health` | none | Basic service and database readiness snapshot | +| `GET` | `/api/v1/status` | none | Service status, environment, ingress, and database posture | +| `GET` | `/api/v1/metrics` | none | Prometheus-compatible metrics | +| `GET` | `/api/v1/integrations/vanta/schema` | `x-api-key` with `read` | Return the Vanta evidence schema | +| `GET` | `/api/v1/integrations/vanta/verification/:receiptId` | `x-api-key` with `read` | Return a normalized Vanta evidence payload | +| `GET` | `/api/v1/registry/sources` | `x-api-key` with `read` | List configured registry sources | +| `POST` | `/api/v1/registry/verify` | `x-api-key` with `verify` | Run a single registry verification request | +| `POST` | `/api/v1/registry/verify-batch` | `x-api-key` with `verify` | Run a batch registry verification request | +| `GET` | `/api/v1/registry/jobs` | `x-api-key` with `read` | List registry job records | +| `GET` | `/api/v1/registry/jobs/:jobId` | `x-api-key` with `read` | Retrieve one registry job record | +| `POST` | `/api/v1/verify/attom` | `x-api-key` with `verify` | Run the Cook County ATTOM cross-check | +| `POST` | `/api/v1/verify` | `x-api-key` with `verify` | Create a verification receipt | +| `GET` | `/api/v1/synthetic` | `x-api-key` with `read` | Return a synthetic bundle for testing | +| `GET` | `/api/v1/receipt/:receiptId` | `x-api-key` with `read` | Retrieve a stored receipt | +| `GET` | `/api/v1/receipt/:receiptId/pdf` | `x-api-key` with `read` | Download a PDF receipt | +| `POST` | `/api/v1/receipt/:receiptId/verify` | `x-api-key` with `read` | Verify receipt integrity and status | +| `POST` | `/api/v1/anchor/:receiptId` | `x-api-key` with `anchor` | Trigger receipt anchoring when allowed | +| `POST` | `/api/v1/receipt/:receiptId/revoke` | `x-api-key` with `revoke` | Revoke a receipt with issuer authorization | +| `GET` | `/api/v1/receipts` | `x-api-key` with `read` | List recent receipts | + +`POST /api/v1/receipt/:receiptId/revoke` also requires these signed issuer headers: + +- `x-issuer-id` +- `x-signature-timestamp` +- `x-issuer-signature` + +## Gateway to Engine Mapping + +These route handlers currently delegate through the engine interface: + +| Method | Path | Gateway Action | Engine Call | +| --- | --- | --- | --- | +| `GET` | `/api/v1/integrations/vanta/verification/:receiptId` | parse `receiptId`, validate auth, shape schema response | `getVantaVerificationResult` | +| `GET` | `/api/v1/registry/sources` | validate auth | `listRegistrySources` | +| `POST` | `/api/v1/registry/verify` | validate body, auth, and source id | `verifyRegistrySource` | +| `POST` | `/api/v1/registry/verify-batch` | validate body, auth, and source ids | `verifyRegistrySources` | +| `GET` | `/api/v1/registry/jobs` | parse limit, validate auth | `listRegistryOracleJobs` | +| `GET` | `/api/v1/registry/jobs/:jobId` | validate auth and route param | `getRegistryOracleJob` | +| `POST` | `/api/v1/verify/attom` | validate deed payload and county guard | `crossCheckAttom` | +| `POST` | `/api/v1/verify` | validate verification payload and map response | `createVerification` | +| `GET` | `/api/v1/synthetic` | validate auth | `createSyntheticBundle` | +| `GET` | `/api/v1/receipt/:receiptId` | parse `receiptId` and map response | `getReceipt` | +| `GET` | `/api/v1/receipt/:receiptId/pdf` | parse `receiptId`, then render PDF from stored receipt | `getReceipt` | +| `POST` | `/api/v1/receipt/:receiptId/verify` | reject body, parse `receiptId` | `getVerificationStatus` | +| `POST` | `/api/v1/anchor/:receiptId` | reject body, parse `receiptId` | `anchorReceipt` | +| `POST` | `/api/v1/receipt/:receiptId/revoke` | reject body, parse `receiptId`, verify issuer headers | `revokeReceipt` | + +Gateway-owned routes that do not call the engine interface directly: + +| Method | Path | Notes | +| --- | --- | --- | +| `GET` | `/api/v1/health` | service health and database readiness | +| `GET` | `/api/v1/status` | service status and deployment posture snapshot | +| `GET` | `/api/v1/metrics` | Prometheus metrics | +| `GET` | `/api/v1/integrations/vanta/schema` | static schema response | +| `GET` | `/api/v1/receipts` | recent receipt listing view | + +## `/v1/*` Endpoints + +| Method | Path | Auth | Purpose | +| --- | --- | --- | --- | +| `POST` | `/v1/verify-bundle` | bearer JWT | Verify a bundle and return the combined result | +| `POST` | `/v1/revoke` | bearer JWT with admin authorization | Revoke a bundle | +| `GET` | `/v1/status/:bundleId` | bearer JWT | Check bundle status | + +## Legacy `/v1/*` Route Behavior + +The `/v1/*` handlers are still present in `src/routes/` and currently use older route dependencies rather than the `apps/api` engine interface. + +| Method | Path | Current Handler Flow | +| --- | --- | --- | +| `POST` | `/v1/verify-bundle` | validate body -> `deps.verifyBundle(...)` -> `recordStore.create(...)` | +| `GET` | `/v1/status/:bundleId` | validate param -> `recordStore.findByBundleHash(...)` | +| `POST` | `/v1/revoke` | validate body and admin claim -> `recordStore.findByBundleHash(...)` -> `anchorNullifier(...)` -> `recordStore.revokeByBundleHash(...)` | + +## Error Semantics + +Integrators should expect these broad response patterns: + +- `400` for schema or request-shape errors +- `401` or `403` for missing credentials, invalid credentials, or missing scope +- `404` for unknown receipts or jobs +- `409` for lifecycle conflicts such as missing preconditions +- `429` for rate limits +- `502` for upstream dependency failures +- `503` when the service is up but a required database path is unavailable + +## Integration Notes + +- Use `/api/v1/*` for receipt-oriented integrations and partner workflows. +- Use `/v1/*` only if you are integrating with the current SDK or an existing JWT-based bundle flow. +- Treat the response payload as the source of technical verification status. Do not infer state from transport success alone. +- The `/api/v1/*` surface is the one that currently matches the public gateway and private engine boundary described elsewhere in this wiki. +- For a concrete payload and response example, see [Quick Verification Example](Quick-Verification-Example). diff --git a/wiki/Claims-Boundary.md b/wiki/Claims-Boundary.md new file mode 100644 index 0000000..7c3c102 --- /dev/null +++ b/wiki/Claims-Boundary.md @@ -0,0 +1,36 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Claims Boundary + +## What TrustSignal Provides + +TrustSignal is evidence integrity infrastructure for compliance artifacts. It provides: + +- signed verification receipts +- verification signals +- receipt lifecycle state +- verifiable provenance for artifacts +- API-accessible verification results + +## What TrustSignal Does Not Provide + +TrustSignal does not provide: + +- legal determinations +- compliance certification +- fraud adjudication +- replacement for system-of-record workflows +- guarantees that depend on private infrastructure evidence + +## Why This Boundary Exists + +TrustSignal operates as an integrity layer within existing workflows. It produces verification artifacts that downstream systems can use for audit, investigation, or compliance evidence validation, while preserving workflow ownership in the upstream system of record. diff --git a/wiki/Evidence-Integrity-Architecture.md b/wiki/Evidence-Integrity-Architecture.md new file mode 100644 index 0000000..82e276c --- /dev/null +++ b/wiki/Evidence-Integrity-Architecture.md @@ -0,0 +1,118 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Evidence Integrity Architecture + +TrustSignal is designed as a bounded verification layer between evidence-producing systems and downstream audit or compliance consumers. + +## Product-Level Architecture + +```mermaid +flowchart LR + A[Evidence Sources] --> B[Compliance Platform] + B --> C[TrustSignal API Gateway] + C --> D[Verification Engine] + D --> E[Signed Receipt] + E --> F[Audit Verification] +``` + +## Public Trust Boundary + +```mermaid +flowchart LR + subgraph EXT[External Partner Systems] + A[Evidence Sources] + B[Compliance or Workflow Platform] + end + + subgraph PUB[TrustSignal Public API Boundary] + C[TrustSignal API Gateway] + end + + subgraph INT[Internal Verification Services] + D[Verification Engine] + E[Receipt Store] + end + + subgraph OUT[Outputs] + F[Signed Receipt] + G[Anchor or Audit Evidence] + end + + A --> B --> C --> D --> E --> F --> G +``` + +This reflects the current public request path implemented in `apps/api/src/server.ts`: the gateway validates and authorizes the request, then delegates major verification lifecycle actions to the engine interface under `apps/api/src/engine/`. + +## Integration Model + +```mermaid +sequenceDiagram + participant P as Partner or Internal App + participant G as TrustSignal API Gateway + participant R as Receipt Layer + + P->>G: Submit verification request + G-->>P: Decision + receiptId + receiptHash + G->>R: Persist receipt and verification state + P->>G: Retrieve or verify receipt later + G-->>P: Receipt data or verification status +``` + +## Verification Lifecycle Flow + +```mermaid +sequenceDiagram + participant C as Client + participant G as API Gateway + participant E as Engine Interface + participant V as Verification Engine + participant S as Receipt Store + + C->>G: POST /api/v1/verify + G->>G: Validate request and auth scope + G->>E: createVerification(...) + E->>V: Execute verification workflow + V->>S: Persist signed receipt and state + E-->>G: receipt + anchor state + G-->>C: Decision + receiptId + receiptHash +``` + +## Boundary Responsibilities + +The public integration boundary is responsible for: + +- authentication and authorization +- request validation +- scoped access control +- rate limiting +- response shaping +- versioned API behavior + +TrustSignal then returns a receipt-oriented result that downstream systems can store or forward. + +The verification engine behind the gateway is intentionally internal. Integrators should depend on the API contract and receipt model rather than internal implementation details. + +## Current Route Boundary + +In the current codebase: + +- the `/api/v1/*` surface follows the gateway-to-engine pattern for major lifecycle actions +- the engine interface currently exposes methods such as `createVerification`, `getReceipt`, `getVerificationStatus`, `getVantaVerificationResult`, `crossCheckAttom`, `anchorReceipt`, and `revokeReceipt` +- the legacy `/v1/*` JWT surface still uses older route dependencies and should be treated as a separate compatibility surface + +## Data Handling Model + +TrustSignal is intended to retain verification artifacts in the form of receipts and related metadata rather than act as a long-term workflow database. Integrators should treat the upstream platform as the operational system of record and TrustSignal as the source of integrity evidence for the verification event. + +## Why This Matters + +Many systems can show that a document was reviewed. Fewer can later show that the result being referenced still corresponds to the same evaluated artifact. TrustSignal closes that gap by turning a verification event into a signed, retrievable artifact with lifecycle state. diff --git a/wiki/FAQ.md b/wiki/FAQ.md new file mode 100644 index 0000000..a072893 --- /dev/null +++ b/wiki/FAQ.md @@ -0,0 +1,56 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# FAQ + +## Is TrustSignal a workflow replacement? + +No. TrustSignal is an integrity layer that fits behind an existing workflow or system of record. + +## What is the main product output? + +The main output is a signed verification receipt that can be retrieved, checked, and attached to downstream audit or compliance workflows. + +## Which API should new integrations use? + +For receipt-oriented integrations in this repository, prefer the `/api/v1/*` surface. The `/v1/*` surface remains available and is used by the current JavaScript SDK. + +## Does TrustSignal provide a JavaScript SDK? + +Yes. The repository includes `@trustsignal/sdk`, which currently targets the `/v1/*` API surface. + +## Can TrustSignal support Vanta evidence workflows? + +Yes. The repository exposes a Vanta schema endpoint and a normalized verification-result endpoint for Vanta-style evidence ingestion. + +## Can receipts be revoked or anchored through the public API? + +Yes. Receipt lifecycle routes include revocation and anchoring operations, subject to the documented authorization model and receipt state requirements. + +## Does TrustSignal make legal or compliance determinations? + +No. TrustSignal provides technical verification signals. It should not be described as legal advice, a certification, or a substitute for independent control validation. + +## Does the public documentation include engine internals? + +No. Public-facing documentation should describe outcomes, integration points, and security boundaries without exposing private implementation details. + +## What should integrators store? + +At minimum, store the `receiptId` and `receiptHash` returned by TrustSignal so the receipt can be retrieved and re-checked later. + +## Should raw PII be exposed or anchored through TrustSignal by default? + +No. Public integrations should minimize sensitive data exposure and avoid anchoring raw personal data unless there is an explicit requirement and supporting controls. + +## Does TrustSignal replace the upstream evidence source? + +No. The upstream platform remains the system of record. TrustSignal adds verifiable provenance around the verification event. diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..1f8253e --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,66 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# TrustSignal Wiki + +TrustSignal provides signed receipts and verifiable provenance for compliance artifacts. It is an integrity layer for existing workflows, not a replacement for the system of record. + +This wiki is written for engineers, technical evaluators, and partner reviewers who need to understand what TrustSignal does, where it fits, and how to integrate with it without exposing private verification-engine details. + +## Start Here + +- [What is TrustSignal](What-is-TrustSignal) +- [Evidence Integrity Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [SDK Usage](SDK-Usage) +- [Vanta Integration Example](Vanta-Integration-Example) +- [Security Model](Security-Model) +- [Threat Model](Threat-Model) +- [FAQ](FAQ) + +## Documentation Scope + +This wiki covers: + +- product positioning and integration model +- public API surfaces +- receipt lifecycle behavior +- SDK usage +- partner-facing security expectations +- threat-model framing for external reviewers + +This wiki does not document: + +- proof internals +- model internals +- private scoring logic +- signing infrastructure implementation details +- internal service topology + +## Key Ideas + +- TrustSignal accepts a verification request from an existing workflow. +- TrustSignal returns a signed verification receipt with stable identifiers. +- That receipt can be retrieved, checked, and attached to downstream audit or compliance workflows. +- TrustSignal can also produce normalized evidence payloads for systems such as Vanta. + +If you want to see the smallest end-to-end payload example first, start with [Quick Verification Example](Quick-Verification-Example). + +## Website + +- https://trustsignal.dev + +## Claims Boundary + +TrustSignal provides technical verification signals, not legal determinations. Public-facing descriptions should avoid claiming completed compliance certification, completed production hardening in every environment, or guarantees that depend on private infrastructure evidence. diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md new file mode 100644 index 0000000..0b05dc2 --- /dev/null +++ b/wiki/Quick-Verification-Example.md @@ -0,0 +1,166 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Quick Verification Example + +This page is written for developers, integration engineers, compliance engineers, and technical partner reviewers who want to understand the smallest realistic TrustSignal verification flow. + +## What This Example Demonstrates + +This example shows how to: + +- submit a verification request to the public TrustSignal API +- receive a decision plus a signed receipt +- inspect the main receipt fields returned by the API +- understand how later re-verification works + +The example uses the current integration-facing receipt workflow on `POST /api/v1/verify`. + +## Verification Lifecycle + +```mermaid +sequenceDiagram + participant C as Client + participant A as TrustSignal API + participant E as Verification Engine + + C->>A: POST /api/v1/verify + A->>E: createVerification(...) + E-->>A: verification result + receipt + A-->>C: JSON response + C->>A: POST /api/v1/receipt/:receiptId/verify + A->>E: getVerificationStatus(...) + E-->>A: current receipt status + A-->>C: integrity and lifecycle status +``` + +## Product Terms and Current API Fields + +The public product language often maps to the current API contract like this: + +| Product Term | Current API Field | +| --- | --- | +| `artifact_hash` | `doc.docHash` | +| `source` | caller-owned workflow or integration context | +| `timestamp` | `timestamp` | +| `control_id` | `policy.profile` | +| `verification_id` | `bundleId` | +| `receipt_id` | `receiptId` | +| `receipt_signature` | `receiptSignature` | +| `status` | `decision` and later verification status | +| `anchor_subject_digest` | `anchor.subjectDigest` | + +## Example Request + +```bash +curl -X POST https://api.trustsignal.example/api/v1/verify \ + -H "Content-Type: application/json" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + -d '{ + "bundleId": "verification-2026-04-18-001", + "transactionType": "compliance_evidence", + "ron": { + "provider": "source-system", + "notaryId": "NOTARY-EXAMPLE-01", + "commissionState": "IL", + "sealPayload": "example-seal-payload" + }, + "doc": { + "docHash": "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" + }, + "property": { + "parcelId": "PARCEL-EXAMPLE-1001", + "county": "Cook", + "state": "IL" + }, + "policy": { + "profile": "CONTROL_CC_001" + }, + "timestamp": "2026-04-18T15:24:00.000Z" + }' +``` + +This example uses: + +- `bundleId` as the caller-controlled verification identifier +- `doc.docHash` as the artifact hash +- `policy.profile` as the control or policy context + +## Example Response + +```json +{ + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": [], + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112" + }, + "revocation": { + "status": "ACTIVE" + } +} +``` + +## Key Response Fields + +- `receiptId`: the durable receipt handle for later retrieval and verification +- `receiptHash`: the integrity digest for the returned receipt payload +- `receiptSignature`: the presence of a signed receipt artifact, without exposing signing infrastructure details +- `anchor.subjectDigest`: the public-facing anchor subject digest when available +- `decision`: the current verification outcome returned by the public API +- `revocation.status`: whether the receipt is currently active or revoked + +## Later Re-Verification + +To check the receipt later, call: + +- `GET /api/v1/receipt/:receiptId` to retrieve the stored receipt +- `POST /api/v1/receipt/:receiptId/verify` to validate current receipt integrity and status + +Example: + +```bash +curl -X POST https://api.trustsignal.example/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a/verify \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +That later check is how downstream systems confirm that the receipt still matches stored verification state rather than relying only on the original response. + +## What This Does Not Expose + +This example intentionally does not expose: + +- proof witness details +- scoring internals +- circuit identifiers +- model outputs +- signing infrastructure specifics +- internal service topology + +The public integration contract is the request, the response, and the receipt lifecycle behavior. + +## Production Readiness + +For production integrations, treat the request/response example as the contract and operationalize these controls: + +- authentication and API keys with scoped access per workflow +- environment configuration for required service dependencies and secure configuration loading +- receipt lifecycle monitoring for new, verified, revoked, and anchored states +- verification checks before relying on earlier results in audit or partner handoff workflows diff --git a/wiki/SDK-Usage.md b/wiki/SDK-Usage.md new file mode 100644 index 0000000..29ea1f2 --- /dev/null +++ b/wiki/SDK-Usage.md @@ -0,0 +1,100 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# SDK Usage + +The repository includes a JavaScript SDK published as `@trustsignal/sdk`. + +## Current Scope + +The current SDK targets the `/v1/*` JWT-authenticated API surface: + +- `POST /v1/verify-bundle` +- `POST /v1/revoke` +- `GET /v1/status/:bundleId` + +For the `/api/v1/*` integration surface, use a standard HTTP client today. + +The constructor option is named `apiKey`, but the SDK sends that value as a Bearer token to the `/v1/*` routes. + +## Install + +```bash +npm install @trustsignal/sdk +``` + +## Initialize the Client + +```ts +import { TrustSignalSDK } from '@trustsignal/sdk'; + +const client = new TrustSignalSDK({ + baseUrl: 'https://api.trustsignal.example', + apiKey: process.env.TRUSTSIGNAL_API_KEY ?? '' +}); +``` + +## Verify a Bundle + +```ts +const result = await client.verify({ + deed_hash: '0x9ccf90f7b62f3ca69f1df442f9e44b6f95ad3f57f5f1d4dce5f35f7915d644a0', + text_length: 4821, + num_signatures: 3, + notary_present: true, + days_since_notarized: 11, + amount: 425000 +}); +``` + +## Revoke a Bundle + +```ts +const revoked = await client.revoke( + '0x9ccf90f7b62f3ca69f1df442f9e44b6f95ad3f57f5f1d4dce5f35f7915d644a0', + 'Court order' +); +``` + +## Check Status + +```ts +const status = await client.status( + '0x9ccf90f7b62f3ca69f1df442f9e44b6f95ad3f57f5f1d4dce5f35f7915d644a0' +); +``` + +## Calling `/api/v1/*` Directly + +For receipt-oriented integrations, direct HTTP is currently the clearest option: + +```ts +const response = await fetch('https://api.trustsignal.example/api/v1/verify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': process.env.TRUSTSIGNAL_API_KEY ?? '' + }, + body: JSON.stringify(payload) +}); + +if (!response.ok) { + throw new Error(`TrustSignal verify failed: ${response.status}`); +} + +const data = await response.json(); +``` + +## Integration Guidance + +- Use the SDK when you already depend on the `/v1/*` bundle contract. +- Use direct HTTP for the `/api/v1/*` receipt lifecycle and Vanta-oriented flows. +- Store identifiers returned by TrustSignal so you can retrieve and re-check receipts later. diff --git a/wiki/Security-Model.md b/wiki/Security-Model.md new file mode 100644 index 0000000..47b8037 --- /dev/null +++ b/wiki/Security-Model.md @@ -0,0 +1,80 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Security Model + +TrustSignal is documented with a security-first posture, but security claims are intentionally bounded. This page summarizes the external-facing controls that are implemented in the repository and the boundaries on what should be claimed publicly. + +See also: [Threat Model](Threat-Model) + +## Control Layers + +```mermaid +flowchart LR + A[Client] --> B[Authentication and Scope Checks] + B --> C[Request Validation] + C --> D[Rate Limits and CORS Controls] + D --> E[Verification and Receipt Issuance] + E --> F[Receipt Retrieval and Lifecycle Checks] +``` + +## Implemented Controls + +- JWT authentication on `/v1/*` +- scoped `x-api-key` authorization on `/api/v1/*` +- request validation at API boundaries +- rate limiting for both global and per-key traffic +- CORS allowlist behavior +- bounded error responses and response shaping +- receipt signing with verification-key support +- production startup guardrails for required configuration +- fail-closed behavior on critical verification paths +- log redaction for sensitive request material +- explicit revocation authorization checks for receipt revocation + +## Data and Privacy Boundaries + +- TrustSignal should not log raw PII unnecessarily. +- Raw sensitive content should not be anchored unless explicitly required and controlled. +- Sensitive transport paths are expected to use TLS in deployed environments. +- Downstream systems should store only the fields they actually need for audit or workflow purposes. + +## Authentication Summary + +| Surface | Auth Model | +| --- | --- | +| `/api/v1/*` | `x-api-key` plus scoped access control | +| `/v1/*` | bearer JWT | +| receipt revocation | `x-api-key` with `revoke` plus issuer signature headers | + +## Operational Claims Boundary + +External documentation should not imply: + +- completed compliance certification without independent evidence +- environment-level encryption or key-custody guarantees without deployment evidence +- legal or policy determinations from TrustSignal outputs alone + +## What This Documentation Intentionally Omits + +This page does not document: + +- private proof implementation details +- internal scoring logic +- signing key infrastructure details +- internal service deployment topology + +Those details are intentionally separated from the public integration model. + +## Related Documentation + +- [Threat Model](Threat-Model) +- [Evidence Integrity Architecture](Evidence-Integrity-Architecture) diff --git a/wiki/Threat-Model.md b/wiki/Threat-Model.md new file mode 100644 index 0000000..008ba92 --- /dev/null +++ b/wiki/Threat-Model.md @@ -0,0 +1,164 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Threat Model + +Related pages: [Security Model](Security-Model) ยท [Evidence Integrity Architecture](Evidence-Integrity-Architecture) + +This page summarizes the external threat model for TrustSignal at the public integration boundary. It is intended for developers, security reviewers, and integration partners evaluating how TrustSignal handles verification requests, receipt lifecycle operations, and downstream evidence use. + +The scope of this page is the public-facing product contract. It does not document proprietary verification engine implementation details. + +## Scope + +In scope: + +- the `/api/v1/*` integration surface +- the `/v1/*` JWT surface +- receipt retrieval, verification, revocation, and anchoring flows +- authentication, authorization, validation, rate limiting, and response behavior +- trust boundaries between client systems, the gateway, stored receipt state, and downstream evidence consumers + +Out of scope: + +- private verification engine implementation details +- proof internals +- scoring logic +- model details +- internal deployment topology + +## Trust Boundaries + +```mermaid +flowchart LR + A[Partner or Client System] --> B[TrustSignal Gateway] + B --> C[Verification and Receipt Layer] + C --> D[Stored Receipt State] + D --> E[Audit or GRC Consumer] + + A -. Auth and scoped access .-> B + B -. Validated, shaped responses .-> A +``` + +## Protected Assets + +- verification receipts and their lifecycle state +- receipt identifiers and hashes used in downstream audit processes +- authorization context for verification, anchoring, and revocation operations +- audit-facing evidence payloads such as the Vanta integration result +- sensitive request content and metadata handled at the API boundary + +## Security Objectives + +- prevent unauthorized access to verification and receipt lifecycle routes +- prevent unauthorized revocation or anchoring actions +- reduce the risk of tampered, stale, or misleading receipt use +- minimize exposure of sensitive request data +- ensure dependency failures are surfaced safely and do not silently downgrade trust + +## Threat Areas + +| Threat Area | Example Risk | Public-Surface Mitigations | +| --- | --- | --- | +| Authentication bypass | Unauthenticated caller reaches protected endpoints | JWT checks on `/v1/*`; scoped `x-api-key` enforcement on `/api/v1/*` | +| Authorization misuse | Caller uses the wrong scope for read, verify, anchor, or revoke actions | Scope checks at the gateway before handler execution | +| Request tampering | Invalid or malformed payloads drive unintended behavior | Schema validation and route-level request guards | +| Receipt misuse | Consumer relies on an outdated or revoked receipt | Receipt verification and lifecycle endpoints expose current state | +| Unauthorized revocation | Caller attempts to revoke a receipt without issuer authority | Revocation requires both API authorization and signed issuer headers | +| Dependency failure masking | Upstream or dependency failures look like successful verification | Explicit error handling and fail-closed behavior on critical paths | +| Abuse or enumeration | Repeated calls attempt discovery or service exhaustion | Global and per-key rate limiting, bounded error responses | +| Sensitive data exposure | Request data or evidence content leaks via logs or responses | Redaction, response shaping, and data minimization guidance | + +## Request Lifecycle Risks + +```mermaid +sequenceDiagram + participant C as Client + participant G as Gateway + participant R as Receipt State + + C->>G: Authenticated request + G->>G: Validate scope and payload + G->>R: Read or update receipt state + R-->>G: Current lifecycle state + G-->>C: Bounded response +``` + +Key lifecycle risks include: + +- creating a receipt from an invalid or malformed request +- retrieving a receipt without the required access scope +- acting on a revoked or missing receipt +- treating a transport success as equivalent to a valid verification outcome + +## Threat Notes by Route Family + +### Verification routes + +Relevant routes: + +- `POST /api/v1/verify` +- `POST /api/v1/verify/attom` +- `POST /v1/verify-bundle` + +Primary concerns: + +- malformed or oversized requests +- unauthorized verification attempts +- unsafe handling of upstream dependency failures + +### Receipt lifecycle routes + +Relevant routes: + +- `GET /api/v1/receipt/:receiptId` +- `GET /api/v1/receipt/:receiptId/pdf` +- `POST /api/v1/receipt/:receiptId/verify` +- `POST /api/v1/receipt/:receiptId/revoke` +- `POST /api/v1/anchor/:receiptId` + +Primary concerns: + +- receipt identifier enumeration +- unauthorized lifecycle changes +- use of stale lifecycle state by downstream systems + +### Evidence export routes + +Relevant routes: + +- `GET /api/v1/integrations/vanta/schema` +- `GET /api/v1/integrations/vanta/verification/:receiptId` + +Primary concerns: + +- exporting evidence for the wrong receipt +- overexposing sensitive information in downstream payloads +- confusing technical verification outputs with legal or compliance determinations + +## Reviewer Guidance + +Security reviewers should focus on: + +- auth and scope enforcement at the route boundary +- bounded and non-sensitive error behavior +- revocation authorization requirements +- receipt lifecycle correctness +- evidence payload minimization +- separation between public gateway code and private verification logic + +## Claims Boundary + +This threat model describes the public product boundary and current code-level controls. It should not be used to claim: + +- a complete certification outcome +- infrastructure guarantees without environment-specific evidence +- formal assurance of private engine behavior that is not documented here diff --git a/wiki/Vanta-Integration-Example.md b/wiki/Vanta-Integration-Example.md new file mode 100644 index 0000000..9734b73 --- /dev/null +++ b/wiki/Vanta-Integration-Example.md @@ -0,0 +1,62 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Vanta Integration Example + +TrustSignal can produce a normalized evidence payload for Vanta-oriented workflows. The goal is to make a verification event portable into a control-evidence system without requiring the downstream system to understand TrustSignal-specific receipt structure. + +## Relevant Endpoints + +- `POST /api/v1/verify` +- `GET /api/v1/integrations/vanta/schema` +- `GET /api/v1/integrations/vanta/verification/:receiptId` + +## Integration Flow + +```mermaid +flowchart LR + A[Partner Workflow] --> B[POST /api/v1/verify] + B --> C[Signed Receipt] + C --> D[GET /api/v1/integrations/vanta/verification/:receiptId] + D --> E[Vanta Evidence Payload] + E --> F[Vanta Control or Audit Workflow] +``` + +## Step by Step + +1. Submit the verification request to `POST /api/v1/verify`. +2. Store the returned `receiptId`. +3. Optionally retrieve the schema from `GET /api/v1/integrations/vanta/schema`. +4. Request the normalized payload from `GET /api/v1/integrations/vanta/verification/:receiptId`. +5. Attach that JSON payload to the relevant Vanta evidence workflow. + +## Auth Model + +- `POST /api/v1/verify` requires `x-api-key` with `verify` +- `GET /api/v1/integrations/vanta/schema` requires `x-api-key` with `read` +- `GET /api/v1/integrations/vanta/verification/:receiptId` requires `x-api-key` with `read` + +## Payload Shape + +The Vanta payload uses schema version `trustsignal.vanta.verification_result.v1` and includes: + +- `vendor` metadata +- `subject` metadata such as `receiptId` and `receiptHash` +- `result` fields such as decision, normalized status, reasons, checks, and risk summary +- `controls` fields such as revocation state, anchor state, and signature presence + +## Why Use the Vanta View + +The Vanta view is useful when a downstream system needs a stable evidence payload instead of the full receipt object. It reduces field-mapping ambiguity and gives partner teams a predictable schema for control evidence ingestion. + +## Claims Boundary + +The Vanta payload is evidence of a technical verification event. It should not be described as a compliance certification or a substitute for control testing performed in the destination system. diff --git a/wiki/Verification-Receipts.md b/wiki/Verification-Receipts.md new file mode 100644 index 0000000..a38f6ea --- /dev/null +++ b/wiki/Verification-Receipts.md @@ -0,0 +1,94 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# Verification Receipts + +A TrustSignal verification receipt is the durable output of a verification event. It provides a stable identifier, a signed payload, and lifecycle state that downstream systems can use as audit evidence. + +## Receipt Lifecycle + +```mermaid +flowchart LR + A[Verification Request] --> B[POST /api/v1/verify] + B --> C[Signed Receipt] + C --> D[GET /api/v1/receipt/:receiptId] + C --> E[POST /api/v1/receipt/:receiptId/verify] + C --> F[POST /api/v1/receipt/:receiptId/revoke] + C --> G[POST /api/v1/anchor/:receiptId] +``` + +## Receipt Verification Flow + +```mermaid +sequenceDiagram + participant C as Client + participant G as API Gateway + participant E as Engine Interface + participant V as Verification Engine + participant S as Stored Receipt + + C->>G: POST /api/v1/receipt/:receiptId/verify + G->>G: Reject unexpected body and parse receiptId + G->>E: getVerificationStatus(receiptId) + E->>V: Load receipt and verify stored state + V->>S: Read stored receipt record + V-->>G: Verification status + G-->>C: Integrity and signature status response +``` + +## Core Receipt Concepts + +| Field | Purpose | +| --- | --- | +| `receiptId` | Stable identifier for retrieval and lifecycle operations | +| `receiptHash` | Canonical digest of the unsigned receipt payload | +| `inputsCommitment` | Digest representing the verification input bundle | +| `decision` | High-level outcome such as `ALLOW`, `FLAG`, or `BLOCK` | +| `checks` | Individual check results included in the receipt payload | +| `reasons` | Human-readable reasons associated with the decision | +| `receiptSignature` | Signature metadata used to verify the receipt payload | +| `revocation.status` | Whether the receipt is still active | +| `anchor.status` | Whether anchoring has occurred for the receipt | + +## Typical Operations + +- Create a receipt with `POST /api/v1/verify` +- Retrieve the stored receipt with `GET /api/v1/receipt/:receiptId` +- Download a PDF rendering with `GET /api/v1/receipt/:receiptId/pdf` +- Re-check integrity and signature status with `POST /api/v1/receipt/:receiptId/verify` +- Revoke a receipt when authorized with `POST /api/v1/receipt/:receiptId/revoke` +- Trigger anchoring when enabled with `POST /api/v1/anchor/:receiptId` + +For a concrete payload example, see [Quick Verification Example](Quick-Verification-Example). + +## Route-Derived Receipt Paths + +The receipt lifecycle implemented in `apps/api/src/server.ts` currently follows this pattern: + +- `GET /api/v1/receipt/:receiptId` calls `getReceipt` +- `GET /api/v1/receipt/:receiptId/pdf` calls `getReceipt`, then renders PDF in the gateway +- `POST /api/v1/receipt/:receiptId/verify` calls `getVerificationStatus` +- `POST /api/v1/anchor/:receiptId` calls `anchorReceipt` +- `POST /api/v1/receipt/:receiptId/revoke` verifies issuer headers, then calls `revokeReceipt` + +## Why Receipts Matter + +The receipt is the bridge between a one-time verification event and later audit use. Instead of relying on screenshots, operator notes, or implicit state in another system, a downstream reviewer can work from a signed artifact with an explicit lifecycle. + +## Practical Guidance + +- Store the `receiptId` and `receiptHash` in the upstream workflow record. +- Treat the receipt as evidence of what TrustSignal evaluated at that point in time. +- Re-run receipt verification before audit submission or partner handoff when current status matters. + +## Claims Boundary + +A receipt is a technical verification artifact. It is not a legal determination, and it does not replace the underlying business process or system-of-record controls. diff --git a/wiki/What-is-TrustSignal.md b/wiki/What-is-TrustSignal.md new file mode 100644 index 0000000..f1bdc56 --- /dev/null +++ b/wiki/What-is-TrustSignal.md @@ -0,0 +1,65 @@ +**Navigation** + +- [Home](Home) +- [What is TrustSignal](What-is-TrustSignal) +- [Architecture](Evidence-Integrity-Architecture) +- [Verification Receipts](Verification-Receipts) +- [API Overview](API-Overview) +- [Claims Boundary](Claims-Boundary) +- [Quick Verification Example](Quick-Verification-Example) +- [Vanta Integration Example](Vanta-Integration-Example) + +# What Is TrustSignal + +TrustSignal is evidence integrity infrastructure for compliance artifacts. It issues signed verification receipts and preserves the information needed to later confirm that a verification result still corresponds to the artifact and policy context originally evaluated. + +## What It Does + +TrustSignal helps teams: + +- create signed receipts when evidence is evaluated +- retrieve those receipts later for audit, review, or partner workflows +- re-verify stored receipts without depending on screenshots or manual notes +- export normalized evidence payloads for downstream systems + +## Where It Fits + +TrustSignal fits behind an existing platform such as: + +- a compliance operations system +- an evidence collection workflow +- a partner portal +- a vertical workflow such as deed verification + +The upstream platform remains the system of record. TrustSignal adds integrity evidence at the boundary. + +## Verification Model + +At a product level, the model is straightforward: + +1. An upstream system submits a verification request or artifact reference. +2. TrustSignal evaluates the request against the configured policy and data dependencies. +3. TrustSignal returns a decision plus a signed verification receipt. +4. Downstream systems use the receipt as a stable audit artifact. +5. Later checks can confirm receipt integrity, status, and lifecycle state. + +In the current codebase, the integration-facing `/api/v1/*` routes implement that model by validating requests in the gateway and delegating the major lifecycle actions to the engine interface. + +## What TrustSignal Is Not + +TrustSignal is not: + +- a replacement for compliance workflow software +- a legal decision engine +- a guarantee that an upstream source system is correct +- a substitute for environment-specific security evidence or control validation + +## Current Repository Context + +This repository currently exposes: + +- the integration-facing `/api/v1/*` API surface +- the legacy `/v1/*` API surface used by the JavaScript SDK +- the DeedShield application module as the current product surface in-repo + +The product framing remains broader than a single module: TrustSignal is the integrity layer that sits behind workflow-specific applications. From b95af78c3c983d9188fb0c0c2c5c83b422582e83 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 15:57:52 -0500 Subject: [PATCH 02/25] feat: add evaluator artifacts and public API contract for adoption --- README.md | 252 ++-- apps/api/openapi.json | 1523 +++++++++++++----------- docs/CANONICAL_MESSAGING.md | 116 ++ docs/PUBLIC_MESSAGING_GUARDRAILS.md | 48 + docs/partner-eval/claims-boundary.md | 31 + docs/partner-eval/integration-model.md | 54 + docs/partner-eval/overview.md | 40 + docs/partner-eval/sample-request.json | 22 + docs/partner-eval/sample-response.json | 22 + docs/partner-eval/security-summary.md | 31 + docs/security-summary.md | 39 + examples/verification-receipt.json | 48 + examples/verification-request.json | 22 + examples/verification-response.json | 22 + examples/verification-status.json | 15 + openapi.yaml | 725 +++++++++++ wiki/API-Overview.md | 132 +- wiki/Claims-Boundary.md | 28 +- wiki/Home.md | 48 +- wiki/Quick-Verification-Example.md | 109 +- wiki/Verification-Receipts.md | 73 +- wiki/What-is-TrustSignal.md | 48 +- 22 files changed, 2271 insertions(+), 1177 deletions(-) create mode 100644 docs/CANONICAL_MESSAGING.md create mode 100644 docs/PUBLIC_MESSAGING_GUARDRAILS.md create mode 100644 docs/partner-eval/claims-boundary.md create mode 100644 docs/partner-eval/integration-model.md create mode 100644 docs/partner-eval/overview.md create mode 100644 docs/partner-eval/sample-request.json create mode 100644 docs/partner-eval/sample-response.json create mode 100644 docs/partner-eval/security-summary.md create mode 100644 docs/security-summary.md create mode 100644 examples/verification-receipt.json create mode 100644 examples/verification-request.json create mode 100644 examples/verification-response.json create mode 100644 examples/verification-status.json create mode 100644 openapi.yaml diff --git a/README.md b/README.md index 535c50b..3518c69 100644 --- a/README.md +++ b/README.md @@ -5,223 +5,143 @@ [![TypeScript](https://img.shields.io/badge/TypeScript-supported-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![Coverage](https://img.shields.io/badge/coverage-threshold%2090%25-0A7F5A)](vitest.config.ts) [![Security Checklist](https://img.shields.io/badge/security-checklist-informational)](SECURITY_CHECKLIST.md) -[![Threat Model](https://img.shields.io/badge/threat_model-documented-informational)](security/threat_model.md) -[![Audit Report](https://img.shields.io/badge/audit_report-available-informational)](security/audit_report.md) Website: https://trustsignal.dev -TrustSignal provides signed receipts and verifiable provenance for compliance artifacts. +TrustSignal is evidence integrity infrastructure for existing workflows. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability without replacing the upstream system of record. -TrustSignal is an integrity layer, not a workflow replacement. It fits into existing compliance workflows without replacing the upstream system of record. +## Problem -## Why TrustSignal Exists +Many teams can show that a file was uploaded, reviewed, or approved. Fewer can later verify that the same artifact is still the one tied to the decision they recorded. -Many teams can show that a file was uploaded, reviewed, or approved. Fewer can later prove that the artifact under review is unchanged from the one originally collected. +That gap creates audit friction, partner review friction, and avoidable evidence disputes. Workflow systems remain important, but they often need a durable verification artifact that can be retrieved and checked later. -TrustSignal addresses that gap by issuing signed receipts and retaining verification state that make later drift detectable and auditable. The product is designed to sit behind an existing intake, evidence, or compliance platform, so the workflow owner keeps control of collection while TrustSignal supplies integrity evidence. +## Integrity Model -## Evidence Integrity Architecture +TrustSignal addresses that gap by accepting a verification request, evaluating it against configured checks, and issuing a signed verification receipt. The receipt gives downstream systems a stable handle for later verification, receipt retrieval, lifecycle checks, and verifiable provenance. -```mermaid -flowchart LR - A[Evidence Sources] --> B[Compliance Platform] - B --> C[TrustSignal] - C --> D[Signed Receipt] - D --> E[Verifiable Audit Evidence] -``` - -## Key Capabilities - -- signed receipt issuance and later receipt verification -- tamper-evident digest comparison and receipt reconstruction -- receipt lifecycle operations for retrieval, revocation, status, and anchoring -- versioned API surfaces for workflow integrations and partner-facing evidence payloads -- registry screening and normalized evidence outcomes for configured sources -- scoped authentication, request validation, rate limiting, and structured logging - -DeedShield is the current application surface in this repository. The broader product framing is evidence integrity infrastructure for compliance artifacts. - -## Example Use Cases +TrustSignal provides: -- issuing signed receipts for compliance artifacts at the point of collection -- re-verifying stored evidence before audit, review, or partner submission -- attaching verifiable provenance to partner-facing evidence payloads -- adding integrity checks to deed and property-record verification workflows without replacing the originating platform +- signed verification receipts +- verification signals +- verifiable provenance metadata +- later verification capability +- existing workflow integration through the public API boundary -## Quickstart / 5-Minute Demo +## Integration Fit -The lowest-risk local path is to run the API and web workspaces and try the product through the existing application surface. +TrustSignal is designed to sit behind an existing workflow such as: -Prerequisites: - -- Node.js `>= 18` -- npm `>= 9` -- PostgreSQL `>= 14` for `apps/api` - -Quickstart: - -```bash -npm install -cp .env.example .env.local -cp apps/api/.env.example apps/api/.env -npm -w apps/api run db:generate -npm -w apps/api run db:push -npm -w apps/api run dev -``` - -Before running `db:push`, set at minimum a valid `DATABASE_URL` in `apps/api/.env`. The API template also includes scoped API-key settings and provider variables for the integration paths you want to exercise. - -In a second terminal: - -```bash -npm -w apps/web run dev -``` - -Default local ports: - -- web app: `http://localhost:3000` -- API: `http://localhost:3001` +- a compliance evidence pipeline +- a partner portal +- an intake or case-management system +- a deed or property-record workflow -What this local path proves: +The upstream platform remains the system of record. TrustSignal adds an integrity layer at the boundary and returns technical verification artifacts that the upstream workflow can store and use later. -- TrustSignal fits behind an existing workflow rather than replacing it -- verification requests produce signed receipts and stored verification state -- receipts can be retrieved and re-verified through the API surface +## Technical Detail -For fuller local setup details, including PostgreSQL configuration and workspace-specific environment guidance, see `apps/api/SETUP.md`. For partnership demo materials, see `docs/partnership/vanta-2026-03-06/README.md`. +The current partner-facing lifecycle in this repository is the `/api/v1/*` surface: -## API Overview +- `POST /api/v1/verify` +- `GET /api/v1/receipt/:receiptId` +- `GET /api/v1/receipt/:receiptId/pdf` +- `POST /api/v1/receipt/:receiptId/verify` +- `POST /api/v1/receipt/:receiptId/revoke` +- `POST /api/v1/anchor/:receiptId` +- `GET /api/v1/receipts` -TrustSignal exposes two main API surfaces in this repository. +Authentication is `x-api-key` with scoped access. Revocation additionally requires issuer authorization headers: `x-issuer-id`, `x-signature-timestamp`, and `x-issuer-signature`. -Core TrustSignal `/v1/*` routes use JWT authentication: +The repository also still includes a legacy JWT-authenticated `/v1/*` surface used by the current JavaScript SDK: - `POST /v1/verify-bundle` -- `POST /v1/revoke` - `GET /v1/status/:bundleId` +- `POST /v1/revoke` -Integration-oriented `/api/v1/*` routes use scoped `x-api-key` access: +## Public API Contract And Examples -- service and status: `GET /api/v1/health`, `GET /api/v1/status`, `GET /api/v1/metrics` -- verification: `POST /api/v1/verify`, `POST /api/v1/verify/attom`, `GET /api/v1/synthetic` -- receipt lifecycle: `GET /api/v1/receipt/:receiptId`, `GET /api/v1/receipt/:receiptId/pdf`, `POST /api/v1/receipt/:receiptId/verify`, `POST /api/v1/receipt/:receiptId/revoke`, `POST /api/v1/anchor/:receiptId`, `GET /api/v1/receipts` -- partner integrations: `GET /api/v1/integrations/vanta/schema`, `GET /api/v1/integrations/vanta/verification/:receiptId` -- registry services: `GET /api/v1/registry/sources`, `POST /api/v1/registry/verify`, `POST /api/v1/registry/verify-batch`, `GET /api/v1/registry/jobs`, `GET /api/v1/registry/jobs/:jobId` +The public evaluation artifacts added in this repo are: -`POST /api/v1/receipt/:receiptId/revoke` also expects signed issuer headers: `x-issuer-id`, `x-signature-timestamp`, and `x-issuer-signature`. +- [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) +- [verification-response.json](/Users/christopher/Projects/trustsignal/examples/verification-response.json) +- [verification-receipt.json](/Users/christopher/Projects/trustsignal/examples/verification-receipt.json) +- [verification-status.json](/Users/christopher/Projects/trustsignal/examples/verification-status.json) +- [partner evaluation kit](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) -The integration model is intentionally narrow: upstream platforms retain workflow ownership, while TrustSignal provides receipt, verification, status, and evidence services at the boundary. +These artifacts document the public verification lifecycle only. They intentionally avoid proof internals, model outputs, circuit identifiers, signing infrastructure specifics, and internal service topology. ## Security Posture -This repository is operated with a security-first posture and explicit claim boundaries. +Public-facing security properties for this repository are: -Implemented controls include: +- scoped API authentication for the integration-facing API +- request validation and rate limiting at the gateway +- signed verification receipts returned with verification responses +- later verification of stored receipt integrity and status +- explicit lifecycle boundaries for read, revoke, and provenance-state operations -- API authentication for both JWT (`/v1/*`) and scoped API-key (`/api/v1/*`) surfaces -- signed receipts returned with verification results -- receipt lifecycle validation for retrieval, verification, status, and revocation state -- revocation controls with issuer authorization requirements -- rate limiting and abuse protection controls -- fail-closed dependency handling on critical verification and startup guardrail paths -- input validation at API boundaries and structured log redaction for selected sensitive fields -- database TLS enforcement checks for production API startup -- primary-source registry guardrails with explicit compliance-gap outcomes +See [docs/security-summary.md](/Users/christopher/Projects/trustsignal/docs/security-summary.md), [SECURITY_CHECKLIST.md](/Users/christopher/Projects/trustsignal/SECURITY_CHECKLIST.md), and [docs/SECURITY.md](/Users/christopher/Projects/trustsignal/docs/SECURITY.md) for the current public-safe security summary and repository guardrails. -Environment and auth requirements are real and enforced. Important variables include: +## What TrustSignal Does Not Claim -- `API_KEYS` and `API_KEY_SCOPES` -- `TRUSTSIGNAL_JWT_SECRETS` or `TRUSTSIGNAL_JWT_SECRET` -- `TRUSTSIGNAL_RECEIPT_SIGNING_PRIVATE_JWK` -- `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWK` -- `TRUSTSIGNAL_RECEIPT_SIGNING_KID` -- optional `TRUSTSIGNAL_RECEIPT_SIGNING_PUBLIC_JWKS` -- `DATABASE_URL` -- `NOTARY_API_KEY`, `PROPERTY_API_KEY`, and `TRUST_REGISTRY_SOURCE` for production verifier configuration -- `TRUSTSIGNAL_ZKP_BACKEND`, `TRUSTSIGNAL_ZKP_PROVER_BIN`, and `TRUSTSIGNAL_ZKP_VERIFIER_BIN` when using external proof infrastructure +TrustSignal does not provide: -Never commit real secrets, API keys, private keys, or local env files. Infrastructure claims such as encrypted-at-rest storage, TLS termination, and key custody require environment-specific evidence outside this repo. +- legal determinations +- compliance certification +- fraud adjudication +- a replacement for the system of record +- infrastructure claims that depend on environment-specific evidence outside this repository -See `SECURITY_CHECKLIST.md`, `security/audit_report.md`, and `security/threat_model.md` for current evidence and open gaps. +## Current Repository Context -## Repository Structure +DeedShield is the current application surface in this repository. The broader product framing remains TrustSignal as evidence integrity infrastructure and an integrity layer for existing workflows. -- `apps/api/`: DeedShield API and integration-facing routes -- `apps/web/`: DeedShield web app -- `src/`: TrustSignal runtime and `/v1/*` API surface -- `packages/core/`: shared receipt, integrity, and verification logic -- `packages/contracts/`: anchoring contract code and related tooling -- `sdk/`: JavaScript SDK for TrustSignal APIs -- `docs/`: canonical product, architecture, operations, and partnership documentation -- `security/`: audit and threat-model artifacts -- `tests/`: API, integration, middleware, and end-to-end coverage -- `circuits/` and `ml/`: supporting implementation and R&D artifacts, not the primary product interface +## Local Development -## Development Workflow - -Primary validation commands: +Prerequisites: -```bash -npm run lint -npm run typecheck -npm test -``` +- Node.js `>= 18` +- npm `>= 9` +- PostgreSQL `>= 14` for `apps/api` -Full validation: +Quickstart: ```bash -npm run validate +npm install +cp .env.example .env.local +cp apps/api/.env.example apps/api/.env +npm -w apps/api run db:generate +npm -w apps/api run db:push +npm -w apps/api run dev ``` -Signed-receipt smoke validation: +In a second terminal: ```bash -npm run smoke:signed-receipt +npm -w apps/web run dev ``` -Development notes: - -- `apps/api/.env.example` is the main local template for API work -- `.env.example` at the repo root supports repo-level scripts and local placeholders -- contract-focused work in `packages/contracts` currently uses Hardhat 3 tooling and should be validated on Node 22+ -- when behavior or posture changes, update the relevant docs and checklists in the same change - -## Documentation - -Start with: - -- `docs/README.md` -- `docs/CANONICAL_MESSAGING.md` -- `PROJECT_PLAN.md` -- `SECURITY_CHECKLIST.md` -- `apps/api/SETUP.md` -- `sdk/README.md` -- `TASKS.md` -- `CHANGELOG.md` - -Canonical architecture, API, and operations guidance also lives under `docs/final/`, including: - -- `docs/final/02_ARCHITECTURE_AND_BOUNDARIES.md` -- `docs/final/03_SECURITY_AND_COMPLIANCE_BASELINE.md` -- `docs/final/05_API_AND_INTEGRATION_GUIDE.md` -- `docs/final/06_PILOT_AND_MARKETPLACE_READINESS.md` -- `docs/final/14_VANTA_INTEGRATION_USE_CASE.md` - -## Claims / Compliance Boundaries +Default local ports: -TrustSignal provides technical verification signals, not legal determinations. +- web app: `http://localhost:3000` +- API: `http://localhost:3001` -TrustSignal should not currently be described as: +## Validation -- a replacement for compliance, audit, or workflow systems -- a finished production-grade document-authenticity proof platform across all surfaces -- a completed compliance certification or a substitute for independent control validation -- a system that should log, expose, or anchor raw PII without explicit need and supporting controls +Relevant repository checks include: -Public messaging must not imply: +```bash +npm run messaging:check +npm run typecheck +npm run build +``` -- completed production readiness without infrastructure evidence -- final cryptographic or proof guarantees where the implementation is still partial or environment-gated -- compliance certifications that are not independently validated +## Documentation Map -This repository includes roadmap and supporting implementation material. Those elements must remain clearly separated from current implemented behavior in any public-facing material. +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [wiki/What-is-TrustSignal.md](/Users/christopher/Projects/trustsignal/wiki/What-is-TrustSignal.md) +- [wiki/API-Overview.md](/Users/christopher/Projects/trustsignal/wiki/API-Overview.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) +- [wiki/Verification-Receipts.md](/Users/christopher/Projects/trustsignal/wiki/Verification-Receipts.md) diff --git a/apps/api/openapi.json b/apps/api/openapi.json index 77a1095..fa80796 100644 --- a/apps/api/openapi.json +++ b/apps/api/openapi.json @@ -1,141 +1,380 @@ { "openapi": "3.0.3", "info": { - "title": "TrustSignal API", - "version": "1.0.0" + "title": "TrustSignal Public Verification API", + "version": "1.0.0", + "description": "TrustSignal is evidence integrity infrastructure for existing workflows.\nThis contract documents the public verification lifecycle for creating signed verification receipts,\nretrieving receipt state, checking later verification status, and managing authorized lifecycle actions.\n" }, - "paths": { - "/api/v1/health": { - "get": { - "summary": "Health check", - "responses": { - "200": { - "description": "OK" - } - } - } + "servers": [ + { + "url": "https://api.trustsignal.dev", + "description": "Production" + } + ], + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], + "tags": [ + { + "name": "Verification", + "description": "Create signed verification receipts and return verification signals." + }, + { + "name": "Receipts", + "description": "Retrieve stored receipts and receipt-ready artifacts." }, + { + "name": "Lifecycle", + "description": "Check later verification status and manage authorized receipt lifecycle actions." + } + ], + "paths": { "/api/v1/verify": { "post": { - "summary": "Verify a notarized bundle", + "tags": [ + "Verification" + ], + "summary": "Create a verification and receive a signed verification receipt", + "description": "Submit a verification request from an existing workflow. TrustSignal returns verification signals,\na signed verification receipt, and verifiable provenance metadata that can be used for later verification.\n", + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VerifyInput" + "$ref": "#/components/schemas/VerificationRequest" + }, + "examples": { + "default": { + "summary": "Verification request", + "value": { + "bundleId": "verification-2026-03-12-001", + "transactionType": "deed_transfer", + "ron": { + "provider": "source-system", + "notaryId": "NOTARY-EXAMPLE-01", + "commissionState": "IL", + "sealPayload": "simulated-seal-payload" + }, + "doc": { + "docHash": "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" + }, + "policy": { + "profile": "CONTROL_CC_001" + }, + "property": { + "parcelId": "PARCEL-EXAMPLE-1001", + "county": "Cook", + "state": "IL" + }, + "timestamp": "2026-03-12T15:24:00.000Z" + } + } } } } }, "responses": { "200": { - "description": "Verification receipt summary", + "description": "Verification completed and a signed verification receipt was issued.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VerifyResponse" + "$ref": "#/components/schemas/VerificationResponse" + }, + "examples": { + "default": { + "summary": "Verification response", + "value": { + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": [ + "receipt issued" + ], + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" + }, + "revocation": { + "status": "ACTIVE" + } + } + } } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } } } }, "/api/v1/receipt/{receiptId}": { "get": { - "summary": "Fetch receipt details", + "tags": [ + "Receipts" + ], + "summary": "Retrieve a stored verification receipt", + "description": "Return the stored receipt view for a previously created verification,\nincluding receipt metadata, the canonical receipt payload, and a PDF URL.\n", + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], "parameters": [ { - "name": "receiptId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } + "$ref": "#/components/parameters/ReceiptId" } ], "responses": { "200": { - "description": "Receipt details", + "description": "Stored receipt returned.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReceiptResponse" + "$ref": "#/components/schemas/VerificationReceipt" } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } } } }, "/api/v1/receipt/{receiptId}/pdf": { "get": { - "summary": "Download receipt PDF", + "tags": [ + "Receipts" + ], + "summary": "Download a PDF rendering of a stored verification receipt", + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], "parameters": [ { - "name": "receiptId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } + "$ref": "#/components/parameters/ReceiptId" } ], "responses": { "200": { - "description": "PDF" + "description": "PDF returned.", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } } } }, "/api/v1/receipt/{receiptId}/verify": { "post": { - "summary": "Verify stored receipt integrity and proof validity", + "tags": [ + "Lifecycle" + ], + "summary": "Check later verification status for a stored receipt", + "description": "Recompute receipt integrity and return the current verification status for later verification.\nThis endpoint does not accept a request body.\n", + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], "parameters": [ { - "name": "receiptId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "$ref": "#/components/parameters/ReceiptId" + } + ], + "responses": { + "200": { + "description": "Receipt verification status returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerificationStatus" + } + } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" + } + } + } + }, + "/api/v1/receipt/{receiptId}/revoke": { + "post": { + "tags": [ + "Lifecycle" + ], + "summary": "Revoke a receipt when the caller is authorized", + "description": "Mark a stored receipt as revoked. This endpoint does not accept a request body.\nIn addition to the API key, issuer authorization headers are required.\n", + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/ReceiptId" + }, + { + "$ref": "#/components/parameters/IssuerId" + }, + { + "$ref": "#/components/parameters/SignatureTimestamp" + }, + { + "$ref": "#/components/parameters/IssuerSignature" } ], "responses": { "200": { - "description": "Receipt integrity and proof verification status", + "description": "Receipt revocation state returned.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReceiptVerifyResponse" + "$ref": "#/components/schemas/RevocationResponse" } } } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } } } }, "/api/v1/anchor/{receiptId}": { "post": { - "summary": "Anchor a receipt hash and proof provenance subject on-chain", + "tags": [ + "Lifecycle" + ], + "summary": "Record verifiable provenance metadata for a receipt when enabled", + "description": "Return the current provenance state for a receipt. This endpoint does not accept a request body.\nIt is intended for workflows that use later verification with anchor subject metadata.\n", + "security": [ + { + "ApiKeyAuth": [ + + ] + } + ], "parameters": [ { - "name": "receiptId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } + "$ref": "#/components/parameters/ReceiptId" } ], "responses": { "200": { - "description": "Anchor status", + "description": "Provenance state returned.", "content": { "application/json": { "schema": { @@ -144,98 +383,227 @@ } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, "409": { - "description": "Proof artifact digest required before anchoring", + "description": "The receipt is not yet in a state that can expose provenance metadata.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" + }, + "example": { + "error": "proof_artifact_required_for_anchor" } } } + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } } } }, "/api/v1/receipts": { "get": { - "summary": "List receipts", - "responses": { - "200": { - "description": "Receipt list" - } - } - } - }, - "/api/v1/synthetic": { - "get": { - "summary": "Get a synthetic sample bundle", - "responses": { - "200": { - "description": "Sample bundle" + "tags": [ + "Receipts" + ], + "summary": "List recent verification receipts", + "description": "Return a compact list of recent receipts for read-scoped integrations.", + "security": [ + { + "ApiKeyAuth": [ + + ] } - } - } - }, - "/api/v1/integrations/vanta/schema": { - "get": { - "summary": "Get the TrustSignal Vanta verification-result schema", + ], "responses": { "200": { - "description": "Vanta schema metadata", + "description": "Receipt list returned.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VantaSchemaResponse" + "type": "array", + "items": { + "$ref": "#/components/schemas/ReceiptListItem" + } } } } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "503": { + "$ref": "#/components/responses/ServiceUnavailable" } } } + } + }, + "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "API key authentication. TrustSignal uses scoped API keys for the public `/api/v1/*` surface.\nTypical scopes are `verify`, `read`, `anchor`, and `revoke`.\n" + } }, - "/api/v1/integrations/vanta/verification/{receiptId}": { - "get": { - "summary": "Get Vanta evidence for a receipt", - "parameters": [ - { - "name": "receiptId", - "in": "path", - "required": true, + "parameters": { + "ReceiptId": { + "name": "receiptId", + "in": "path", + "required": true, + "description": "Receipt identifier returned by `POST /api/v1/verify`.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + "IssuerId": { + "name": "x-issuer-id", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Authorized issuer identifier for receipt revocation." + }, + "SignatureTimestamp": { + "name": "x-signature-timestamp", + "in": "header", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "Timestamp used for issuer revocation authorization." + }, + "IssuerSignature": { + "name": "x-issuer-signature", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Issuer authorization signature for receipt revocation." + } + }, + "responses": { + "BadRequest": { + "description": "Request validation failed.", + "content": { + "application/json": { "schema": { - "type": "string", - "format": "uuid" + "$ref": "#/components/schemas/ErrorResponse" } } - ], - "responses": { - "200": { - "description": "Vanta verification evidence", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VantaVerificationResult" - } - } + } + }, + "Unauthorized": { + "description": "Missing or invalid authentication material.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "Forbidden": { + "description": "The caller is authenticated but not authorized for this operation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "NotFound": { + "description": "Requested verification receipt was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "TooManyRequests": { + "description": "Rate limit exceeded.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "ServiceUnavailable": { + "description": "A required service dependency is unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } } - } - }, - "components": { + }, "schemas": { - "VerifyInput": { + "VerificationRequest": { "type": "object", + "additionalProperties": false, + "required": [ + "bundleId", + "transactionType", + "ron", + "doc", + "policy", + "property" + ], "properties": { "bundleId": { - "type": "string" + "type": "string", + "minLength": 1, + "description": "Caller-controlled verification identifier." }, "transactionType": { - "type": "string" + "type": "string", + "minLength": 1, + "description": "Existing workflow transaction category." }, "ron": { "type": "object", + "additionalProperties": false, + "required": [ + "provider", + "notaryId", + "commissionState", + "sealPayload" + ], "properties": { "provider": { "type": "string" @@ -244,747 +612,512 @@ "type": "string" }, "commissionState": { - "type": "string" + "type": "string", + "minLength": 2, + "maxLength": 2 }, "sealPayload": { "type": "string" + }, + "sealScheme": { + "type": "string", + "enum": [ + "SIM-ECDSA-v1" + ] } - }, - "required": [ - "provider", - "notaryId", - "commissionState", - "sealPayload" - ] + } }, "doc": { "type": "object", + "additionalProperties": false, + "required": [ + "docHash" + ], "properties": { "docHash": { - "type": "string" + "type": "string", + "description": "Artifact hash supplied by the caller." + }, + "pdfBase64": { + "type": "string", + "description": "Optional artifact payload when the integration sends the full document." } - }, - "required": [ - "docHash" - ] + } }, "policy": { "type": "object", + "additionalProperties": false, + "required": [ + "profile" + ], "properties": { "profile": { - "type": "string" + "type": "string", + "description": "Policy or control identifier for the verification." } - }, + } + }, + "property": { + "type": "object", + "additionalProperties": false, "required": [ - "profile" - ] - } - }, - "required": [ - "bundleId", - "transactionType", - "ron", - "doc", - "policy" - ] - }, - "ErrorResponse": { - "type": "object", - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - } - }, - "required": [ - "error" - ] - }, - "FraudSignal": { - "type": "object", - "additionalProperties": true + "parcelId", + "county", + "state" + ], + "properties": { + "parcelId": { + "type": "string" + }, + "county": { + "type": "string" + }, + "state": { + "type": "string", + "minLength": 2, + "maxLength": 2 + } + } + }, + "ocrData": { + "type": "object", + "additionalProperties": false, + "properties": { + "notaryName": { + "type": "string" + }, + "notaryCommissionId": { + "type": "string" + }, + "propertyAddress": { + "type": "string" + }, + "grantorName": { + "type": "string" + } + } + }, + "registryScreening": { + "type": "object", + "additionalProperties": false, + "properties": { + "subjectName": { + "type": "string", + "minLength": 2, + "maxLength": 256 + }, + "sourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "maxItems": 50 + }, + "forceRefresh": { + "type": "boolean" + } + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Caller-provided event timestamp." + } + } }, - "FraudRisk": { + "VerificationResponse": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, + "required": [ + "receiptVersion", + "decision", + "reasons", + "receiptId", + "receiptHash", + "anchor", + "revocation" + ], "properties": { - "score": { - "type": "number", - "minimum": 0, - "maximum": 1 + "receiptVersion": { + "type": "string", + "example": "2.0" }, - "band": { + "decision": { "type": "string", "enum": [ - "LOW", - "MEDIUM", - "HIGH" - ] + "ALLOW", + "FLAG", + "BLOCK" + ], + "description": "Verification signal for the submitted request." }, - "signals": { + "reasons": { "type": "array", "items": { - "$ref": "#/components/schemas/FraudSignal" + "type": "string" } + }, + "receiptId": { + "type": "string", + "format": "uuid" + }, + "receiptHash": { + "type": "string" + }, + "receiptSignature": { + "$ref": "#/components/schemas/ReceiptSignature" + }, + "anchor": { + "$ref": "#/components/schemas/AnchorState" + }, + "revocation": { + "$ref": "#/components/schemas/RevocationState" } }, - "required": [ - "score", - "band", - "signals" - ] + "description": "Public response fields for receipt issuance. Additional implementation-specific fields may also be present.\n" }, - "RevocationState": { - "type": "object", - "additionalProperties": false, - "properties": { - "status": { - "type": "string", - "enum": [ - "ACTIVE", - "REVOKED" - ] + "VerificationReceipt": { + "allOf": [ + { + "$ref": "#/components/schemas/VerificationResponse" + }, + { + "type": "object", + "additionalProperties": true, + "required": [ + "receipt", + "canonicalReceipt", + "pdfUrl" + ], + "properties": { + "receipt": { + "$ref": "#/components/schemas/StoredReceipt" + }, + "canonicalReceipt": { + "type": "string", + "description": "Canonical receipt payload used for later verification." + }, + "pdfUrl": { + "type": "string", + "description": "Relative URL for the PDF rendering of the receipt." + } + } } - }, - "required": [ - "status" ] }, - "AnchorState": { + "StoredReceipt": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, + "required": [ + "receiptVersion", + "receiptId", + "createdAt", + "policyProfile", + "inputsCommitment", + "checks", + "decision", + "reasons", + "receiptHash" + ], "properties": { - "status": { - "type": "string", - "description": "Anchoring lifecycle only. This does not imply the proof is cryptographically verifiable." + "receiptVersion": { + "type": "string" }, - "backend": { + "receiptId": { "type": "string", - "description": "Present on mapped receipt/verify responses. The direct anchor route currently omits this field." + "format": "uuid" }, - "txHash": { - "type": "string" + "createdAt": { + "type": "string", + "format": "date-time" }, - "chainId": { + "policyProfile": { "type": "string" }, - "anchorId": { + "inputsCommitment": { "type": "string" }, - "anchoredAt": { - "type": "string", - "format": "date-time" - }, - "subjectDigest": { - "type": "string", - "description": "Digest of the anchor provenance subject derived from receipt hash and attestation metadata." + "checks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CheckResult" + } }, - "subjectVersion": { + "decision": { "type": "string", "enum": [ - "trustsignal.anchor_subject.v1" + "ALLOW", + "FLAG", + "BLOCK" ] + }, + "reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "receiptHash": { + "type": "string" + }, + "receiptSignature": { + "$ref": "#/components/schemas/ReceiptSignature" } }, - "required": [ - "status", - "subjectDigest", - "subjectVersion" - ] + "description": "Stored receipt representation returned by the gateway. Integrations should rely on the documented receipt fields\nand should not infer internal engine behavior from optional fields.\n" }, - "ZkpPublicInputs": { + "VerificationStatus": { "type": "object", "additionalProperties": false, + "required": [ + "verified", + "integrityVerified", + "signatureVerified", + "signatureStatus", + "proofVerified", + "recomputedHash", + "storedHash", + "inputsCommitment", + "receiptSignature", + "revoked" + ], "properties": { - "policyHash": { - "type": "string" + "verified": { + "type": "boolean", + "description": "Overall later verification result." }, - "timestamp": { + "integrityVerified": { + "type": "boolean", + "description": "Whether the stored receipt still matches its canonical hash." + }, + "signatureVerified": { + "type": "boolean", + "description": "Whether the stored receipt signature verified." + }, + "signatureStatus": { "type": "string", - "format": "date-time" + "enum": [ + "verified", + "invalid", + "unknown-kid", + "legacy-unsigned" + ] }, - "inputsCommitment": { + "signatureReason": { "type": "string" }, - "conformance": { + "proofVerified": { "type": "boolean", - "description": "Policy conformance result only. No raw witness or PII-bearing fields are exposed." - }, - "declaredDocHash": { - "type": "string" + "description": "Public verification status flag returned by the API." }, - "documentDigest": { + "recomputedHash": { "type": "string" }, - "documentCommitment": { + "storedHash": { "type": "string" }, - "schemaVersion": { + "inputsCommitment": { "type": "string" }, - "documentWitnessMode": { - "type": "string", - "enum": [ - "canonical-document-bytes-v1", - "declared-doc-hash-v1" + "receiptSignature": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReceiptSignatureSummary" + }, + { + "type": "null" + } ] - } - }, - "required": [ - "policyHash", - "timestamp", - "inputsCommitment", - "conformance", - "declaredDocHash", - "documentDigest", - "documentCommitment", - "schemaVersion", - "documentWitnessMode" - ] - }, - "DevOnlyProofArtifact": { - "type": "object", - "additionalProperties": false, - "description": "Digest of the dev-only document attestation artifact. This is not a serialized proof.", - "properties": { - "format": { - "type": "string" }, - "digest": { - "type": "string" + "revoked": { + "type": "boolean" } - }, - "required": [ - "format", - "digest" - ] + } }, - "VerifiableProofArtifact": { + "AnchorState": { "type": "object", - "additionalProperties": false, - "description": "Serialized proof artifact for a verifiable Halo2 attestation.", + "additionalProperties": true, + "required": [ + "status" + ], "properties": { - "format": { - "type": "string" + "status": { + "type": "string", + "description": "Current provenance state for the receipt." }, - "digest": { - "type": "string" + "anchoredAt": { + "type": "string", + "format": "date-time" }, - "encoding": { + "subjectDigest": { "type": "string", - "enum": [ - "base64" - ] + "description": "Verifiable provenance digest for later verification." }, - "proof": { - "type": "string" + "subjectVersion": { + "type": "string", + "description": "Version label for the provenance digest format." } }, - "required": [ - "format", - "digest", - "encoding", - "proof" - ] + "description": "Public-safe provenance metadata. Implementations may return additional fields, but integrations should treat\nthis object as provenance state rather than infrastructure detail.\n" }, - "DevOnlyZkpAttestation": { + "ReceiptListItem": { "type": "object", "additionalProperties": false, - "description": "Development-only document attestation metadata. Presence of this object does not mean the proof is cryptographically verifiable.", + "required": [ + "receiptId", + "decision", + "createdAt", + "anchorStatus", + "revoked" + ], "properties": { - "proofId": { - "type": "string" - }, - "scheme": { + "receiptId": { "type": "string", - "enum": [ - "HALO2-DEV-v0" - ] + "format": "uuid" }, - "status": { + "decision": { "type": "string", "enum": [ - "dev-only" + "ALLOW", + "FLAG", + "BLOCK" ] }, - "backend": { + "createdAt": { "type": "string", - "enum": [ - "halo2-dev" - ] + "format": "date-time" }, - "circuitId": { + "anchorStatus": { "type": "string" }, - "publicInputs": { - "$ref": "#/components/schemas/ZkpPublicInputs" - }, - "proofArtifact": { - "$ref": "#/components/schemas/DevOnlyProofArtifact" + "revoked": { + "type": "boolean" } - }, - "required": [ - "proofId", - "scheme", - "status", - "backend", - "circuitId", - "publicInputs", - "proofArtifact" - ] + } }, - "VerifiableZkpAttestation": { + "RevocationState": { "type": "object", "additionalProperties": false, - "description": "Cryptographically verifiable Halo2 document attestation.", + "required": [ + "status" + ], "properties": { - "proofId": { - "type": "string" - }, - "scheme": { + "status": { "type": "string", "enum": [ - "HALO2-v1" + "ACTIVE", + "REVOKED", + "ALREADY_REVOKED" ] - }, + } + } + }, + "RevocationResponse": { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { "status": { "type": "string", "enum": [ - "verifiable" + "REVOKED", + "ALREADY_REVOKED" ] }, - "backend": { - "type": "string", - "enum": [ - "halo2" - ] - }, - "circuitId": { - "type": "string" - }, - "verificationKeyId": { - "type": "string" - }, - "verifiedAt": { + "issuerId": { "type": "string", - "format": "date-time" - }, - "publicInputs": { - "$ref": "#/components/schemas/ZkpPublicInputs" - }, - "proofArtifact": { - "$ref": "#/components/schemas/VerifiableProofArtifact" - } - }, - "required": [ - "proofId", - "scheme", - "status", - "backend", - "circuitId", - "verificationKeyId", - "verifiedAt", - "publicInputs", - "proofArtifact" - ] - }, - "ZkpAttestation": { - "oneOf": [ - { - "$ref": "#/components/schemas/DevOnlyZkpAttestation" - }, - { - "$ref": "#/components/schemas/VerifiableZkpAttestation" - } - ], - "discriminator": { - "propertyName": "status", - "mapping": { - "dev-only": "#/components/schemas/DevOnlyZkpAttestation", - "verifiable": "#/components/schemas/VerifiableZkpAttestation" + "description": "Authorized issuer identifier returned when the receipt is revoked." } } }, - "VerifyResponse": { + "ReceiptSignature": { "type": "object", "additionalProperties": false, + "required": [ + "alg", + "kid", + "signature" + ], "properties": { - "receiptVersion": { + "alg": { "type": "string", "enum": [ - "2.0" + "EdDSA" ] }, - "decision": { + "kid": { "type": "string" }, - "reasons": { - "type": "array", - "items": { - "type": "string" - } - }, - "receiptId": { + "signature": { "type": "string", - "format": "uuid" - }, - "receiptHash": { - "type": "string" - }, - "proofVerified": { - "type": "boolean", - "description": "Present when the API can truthfully report proof verification status without a separate verifier round-trip. Dev-only or missing attestations return false." - }, - "anchor": { - "$ref": "#/components/schemas/AnchorState" - }, - "fraudRisk": { - "$ref": "#/components/schemas/FraudRisk" - }, - "zkpAttestation": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/ZkpAttestation" - } - ] - }, - "revocation": { - "$ref": "#/components/schemas/RevocationState" - }, - "deprecated": { - "type": "object", - "additionalProperties": false, - "properties": { - "riskScore": { - "type": "integer" - }, - "revoked": { - "type": "boolean" - } - } - } - }, - "required": [ - "receiptVersion", - "decision", - "reasons", - "receiptId", - "receiptHash", - "anchor", - "fraudRisk", - "zkpAttestation", - "revocation" - ] - }, - "ReceiptResponse": { - "allOf": [ - { - "$ref": "#/components/schemas/VerifyResponse" - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "receipt": { - "type": "object", - "additionalProperties": true - }, - "canonicalReceipt": { - "type": "string" - }, - "pdfUrl": { - "type": "string" - } - }, - "required": [ - "receipt", - "canonicalReceipt", - "pdfUrl" - ] + "description": "Signed receipt artifact returned by the API." } - ] + } }, - "ReceiptVerifyResponse": { + "ReceiptSignatureSummary": { "type": "object", "additionalProperties": false, - "description": "Integrity verification is distinct from proof verification. A dev-only attestation yields proofVerified=false.", + "required": [ + "alg", + "kid" + ], "properties": { - "verified": { - "type": "boolean" - }, - "integrityVerified": { - "type": "boolean" - }, - "proofVerified": { - "type": "boolean" - }, - "recomputedHash": { - "type": "string" - }, - "storedHash": { - "type": "string" + "alg": { + "type": "string", + "enum": [ + "EdDSA" + ] }, - "inputsCommitment": { + "kid": { "type": "string" - }, - "revoked": { - "type": "boolean" } - }, - "required": [ - "verified", - "integrityVerified", - "proofVerified", - "recomputedHash", - "storedHash", - "inputsCommitment", - "revoked" - ] + } }, - "VantaCheck": { + "CheckResult": { "type": "object", "additionalProperties": false, + "required": [ + "checkId", + "status" + ], "properties": { "checkId": { "type": "string" }, "status": { - "type": "string" + "type": "string", + "enum": [ + "PASS", + "FAIL", + "WARN" + ] }, "details": { "type": "string" - }, - "source_name": { - "type": "string" } - }, - "required": [ - "checkId", - "status" - ] + } }, - "VantaFraudRisk": { + "ErrorResponse": { "type": "object", - "nullable": true, - "additionalProperties": false, - "properties": { - "score": { - "type": "number" - }, - "band": { - "type": "string" - }, - "reasons": { - "type": "array", - "items": { - "type": "string" - } - } - }, + "additionalProperties": true, "required": [ - "score", - "band", - "reasons" - ] - }, - "VantaControls": { - "type": "object", - "additionalProperties": false, + "error" + ], "properties": { - "revoked": { - "type": "boolean" - }, - "anchorStatus": { + "error": { "type": "string" }, - "anchored": { - "type": "boolean" - }, - "anchorSubjectDigest": { + "message": { "type": "string" }, - "anchorSubjectVersion": { - "type": "string", - "enum": [ - "trustsignal.anchor_subject.v1" - ] - }, - "anchoredAt": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "revoked", - "anchorStatus", - "anchored" - ] - }, - "VantaVerificationResult": { - "type": "object", - "additionalProperties": false, - "properties": { - "schemaVersion": { - "type": "string", - "enum": [ - "trustsignal.vanta.verification_result.v1" - ] - }, - "generatedAt": { - "type": "string", - "format": "date-time" - }, - "vendor": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "enum": [ - "TrustSignal" - ] - }, - "module": { - "type": "string", - "enum": [ - "DeedShield" - ] - }, - "environment": { - "type": "string" - }, - "apiVersion": { - "type": "string", - "enum": [ - "v1" - ] - } - }, - "required": [ - "name", - "module", - "environment", - "apiVersion" - ] - }, - "subject": { - "type": "object", - "additionalProperties": false, - "properties": { - "receiptId": { - "type": "string" - }, - "receiptHash": { - "type": "string" - }, - "policyProfile": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "receiptId", - "receiptHash", - "policyProfile", - "createdAt" - ] - }, - "result": { - "type": "object", - "additionalProperties": false, - "properties": { - "decision": { - "type": "string", - "enum": [ - "ALLOW", - "FLAG", - "BLOCK" - ] - }, - "normalizedStatus": { - "type": "string", - "enum": [ - "PASS", - "REVIEW", - "FAIL" - ] - }, - "riskScore": { - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "reasons": { - "type": "array", - "items": { - "type": "string" - } - }, - "checks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/VantaCheck" - } - }, - "fraudRisk": { - "$ref": "#/components/schemas/VantaFraudRisk" - }, - "zkpAttestation": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/ZkpAttestation" - } - ] - } - }, - "required": [ - "decision", - "normalizedStatus", - "riskScore", - "reasons", - "checks", - "fraudRisk", - "zkpAttestation" - ] - }, - "controls": { - "$ref": "#/components/schemas/VantaControls" - } - }, - "required": [ - "schemaVersion", - "generatedAt", - "vendor", - "subject", - "result", - "controls" - ] - }, - "VantaSchemaResponse": { - "type": "object", - "additionalProperties": false, - "properties": { - "schemaVersion": { - "type": "string", - "enum": [ - "trustsignal.vanta.verification_result.v1" - ] - }, - "schema": { - "type": "object", - "additionalProperties": true + "details": { + "description": "Optional validation or request context." } - }, - "required": [ - "schemaVersion", - "schema" - ] + } } } } -} +} \ No newline at end of file diff --git a/docs/CANONICAL_MESSAGING.md b/docs/CANONICAL_MESSAGING.md new file mode 100644 index 0000000..f453e4d --- /dev/null +++ b/docs/CANONICAL_MESSAGING.md @@ -0,0 +1,116 @@ +# TrustSignal Canonical Messaging + +This document is the messaging source of truth for TrustSignal across the three-repo system. + +- `trustsignal` defines implementation truth. +- `TrustSignal-docs` is the public documentation layer derived from implementation truth. +- `v0-signal-new` is the public website and presentation layer derived from approved messaging and public-safe docs. + +Public messaging may simplify. It may not contradict the codebase, overstate experimental work, or present roadmap items as shipped behavior. + +## Canonical Narrative + +TrustSignal is evidence integrity infrastructure for existing workflows. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability. TrustSignal is designed to strengthen existing systems of record and partner workflows by adding durable verification artifacts rather than replacing those systems. Deed verification, compliance evidence, and future credentialing flows are examples of where the integrity layer fits. + +## Messaging Hierarchy + +### Lead Story + +- Evidence integrity infrastructure for existing workflows +- Signed verification receipts at the point of evaluation +- Verification signals and audit-ready evidence +- Verifiable provenance and later verification + +### Supporting Proof Points + +- TrustSignal sits behind an existing workflow instead of replacing it +- Auditors and operators need durable verification artifacts, not just workflow notes +- The product fits evidence collection, deeds, compliance records, and credential workflows +- Public messaging should show the integrity model before deeper architecture + +### Technical Depth Layer + +- Signed verification receipts, digest comparison, receipt retrieval, later verification, and revocation controls +- Registry integrations and evidence payloads may be discussed where implementation-backed +- Provenance-state retrieval may appear after the core integrity model is clear + +### Roadmap Layer + +- More advanced proof systems +- Expanded provenance portability +- Deeper registry and analytics work +- Any future AI-related expansion + +## What To Reject + +### Entity Confusion + +- Do not collapse TrustSignal, DeedShield, Vanta, healthcare, and future marketplaces into one undifferentiated story +- Do not let the deed wedge define the entire product +- Do not describe TrustSignal as a replacement for the system that collected the evidence + +### Unsupported Precision + +- Do not use exact performance, fraud-detection, or coverage numbers unless currently verified and reproducible +- Do not use market-loss statistics as the primary proof of product value +- Do not publish exact technical claims that imply more than the implementation supports + +### Claims Requiring Repo Verification + +- Do not present production readiness as complete without infrastructure evidence +- Do not present experimental or dev-only proof paths as public guarantees +- Do not present roadmap architecture as shipped behavior + +## Public Website Structure + +1. Hero: evidence integrity infrastructure for existing workflows +2. Problem: why integrity drift and audit gaps matter +3. Integrity model: signed verification receipts and verification signals +4. Integration fit: how TrustSignal sits behind an existing workflow +5. Use cases: deeds, compliance evidence, credentialing, other high-trust records +6. Technical detail: implementation-backed foundations only +7. Security boundary: what the public site does and does not expose +8. Pilot CTA and intake +9. Legal, privacy, and security pages + +## README Structure + +1. Title and one-sentence positioning +2. Problem +3. Integrity model +4. Integration fit +5. Technical detail +6. Public API contract and examples +7. Security posture +8. What is explicitly not claimed +9. Local development +10. Validation +11. Documentation map + +## Claim Rules + +### Allowed Now + +- TrustSignal is evidence integrity infrastructure +- TrustSignal adds signed verification receipts +- TrustSignal returns verification signals +- TrustSignal provides verifiable provenance and later verification +- TrustSignal fits behind an existing workflow with low integration friction +- TrustSignal strengthens evidence and compliance pipelines instead of replacing them +- Deed verification is one use case + +### Allowed Only With Qualification + +- Registry verification or screening +- Revocation controls +- Provenance-state retrieval +- SDK or integration ergonomics +- Pilot readiness or enterprise readiness +- Test or performance metrics + +### Roadmap Only + +- Broad AI fraud detection as the lead product story +- Full production-grade document authenticity guarantees beyond current implementation +- Marketplace-ready claims without evidence-backed controls and validation +- Any claim implying private infrastructure or advanced proof architecture is complete if it is still partial, gated, or experimental diff --git a/docs/PUBLIC_MESSAGING_GUARDRAILS.md b/docs/PUBLIC_MESSAGING_GUARDRAILS.md new file mode 100644 index 0000000..27f3ae9 --- /dev/null +++ b/docs/PUBLIC_MESSAGING_GUARDRAILS.md @@ -0,0 +1,48 @@ +# Public Messaging Guardrails + +This document is the canonical messaging policy for public-facing TrustSignal content derived from the source-of-truth repo. + +## Canonical Positioning + +TrustSignal is evidence integrity infrastructure. + +Public-facing material should lead with: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration +- integrity layer positioning + +Public-facing material should not lead with: + +- advanced proof architecture +- infrastructure implementation details +- unsupported compliance claims +- roadmap mechanics presented as shipped behavior +- product language that implies TrustSignal replaces the system of record + +## Scope + +These guardrails apply to public-facing or buyer-facing files in `trustsignal`, including: + +- root `README.md` +- canonical messaging or legal docs intended for reuse +- public web surfaces under `apps/web/src/app` +- partner evaluation kit materials under `docs/partner-eval/` + +## Review Rules + +- If a term appears only as a negative rule, caution, or qualification, it may stay. +- If a term appears as product positioning or as a lead claim, remove or rewrite it. +- If a statement depends on optional or environment-gated behavior, qualify it. +- If a claim cannot be tied back to implementation truth, remove it from public-facing material. + +## Check Command + +Run the messaging check before merging messaging-heavy changes: + +```bash +npm run messaging:check +``` diff --git a/docs/partner-eval/claims-boundary.md b/docs/partner-eval/claims-boundary.md new file mode 100644 index 0000000..6566512 --- /dev/null +++ b/docs/partner-eval/claims-boundary.md @@ -0,0 +1,31 @@ +# Partner Claims Boundary + +## Problem + +Partners need a clear view of what TrustSignal does and does not claim so they can evaluate fit without over-reading the verification response. + +## Integrity Model + +TrustSignal is evidence integrity infrastructure. It returns technical verification artifacts that support later verification inside an existing workflow. + +## Integration Fit + +TrustSignal provides: + +- signed verification receipts +- verification signals +- verifiable provenance metadata +- later verification capability +- an integrity layer for existing workflows + +## Technical Detail + +TrustSignal does not provide: + +- legal determinations +- compliance certification +- fraud adjudication +- a replacement for the system of record +- partner-specific control attestations that depend on infrastructure outside this repository + +Public integration materials should treat the TrustSignal response as a technical verification artifact, not as a legal, regulatory, or adjudicative decision. diff --git a/docs/partner-eval/integration-model.md b/docs/partner-eval/integration-model.md new file mode 100644 index 0000000..8a2f178 --- /dev/null +++ b/docs/partner-eval/integration-model.md @@ -0,0 +1,54 @@ +# Partner Integration Model + +## Problem + +Most partner integrations already have an intake flow, case record, or evidence record. The missing layer is a stable verification artifact that can be retrieved and checked later. + +## Integrity Model + +TrustSignal accepts caller-supplied verification context and returns: + +- a verification decision +- signed verification receipts +- verification signals for downstream workflow logic +- verifiable provenance metadata for later verification + +## Integration Fit + +A typical partner integration looks like this: + +1. The partner system computes or supplies the artifact hash. +2. The partner posts the verification request to `POST /api/v1/verify`. +3. TrustSignal returns a signed verification receipt and verification signals. +4. The partner stores the `receiptId`, `receiptHash`, and decision with its own workflow record. +5. Before audit, handoff, or dispute review, the partner calls `POST /api/v1/receipt/{receiptId}/verify` for later verification. + +## Request Inputs + +The current public verification request includes these core fields: + +- `bundleId`: caller-controlled verification identifier +- `transactionType`: workflow category +- `doc.docHash`: artifact hash +- `policy.profile`: policy or control identifier +- `property`: workflow-specific subject context for the current DeedShield surface +- `timestamp`: caller-provided event time when available + +## Response Outputs + +The current public verification response includes these core fields: + +- `decision`: verification signal +- `reasons`: human-readable response reasons +- `receiptId`: durable receipt handle +- `receiptHash`: digest for the receipt payload +- `receiptSignature`: signed receipt artifact +- `anchor.subjectDigest`: provenance digest when available +- `revocation.status`: current receipt lifecycle state + +## Operational Notes + +- Authentication is an `x-api-key` with scoped access. +- Receipt retrieval and later verification are separate read operations. +- Revocation is an authorized lifecycle action and requires issuer authorization headers in addition to the API key. +- TrustSignal is an existing workflow integration layer, not a replacement for the partner's system of record. diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md new file mode 100644 index 0000000..1dbc2a3 --- /dev/null +++ b/docs/partner-eval/overview.md @@ -0,0 +1,40 @@ +# TrustSignal Partner Evaluation Overview + +## Problem + +Teams often have a workflow record that says an artifact was reviewed, approved, or submitted, but they cannot easily prove later that the same artifact is still the one tied to that decision. + +## Integrity Model + +TrustSignal is evidence integrity infrastructure. It acts as an integrity layer for existing workflows by accepting a verification request, returning verification signals, and issuing signed verification receipts with verifiable provenance metadata for later verification. + +TrustSignal is designed to support: + +- signed verification receipts +- verification signals +- verifiable provenance +- audit-ready evidence +- later verification without replacing the upstream workflow owner + +## Integration Fit + +TrustSignal fits behind an existing workflow such as: + +- a partner portal +- a compliance evidence pipeline +- a deed or property-record workflow +- another intake system that already owns collection and review + +The upstream platform remains the system of record. TrustSignal adds an integrity layer and returns technical verification artifacts that can be stored alongside the workflow record. + +## Technical Detail + +The public evaluation path in this repository is the `/api/v1/*` surface: + +1. Submit a verification request to `POST /api/v1/verify`. +2. Receive a decision, signed verification receipt, and provenance metadata. +3. Retrieve the stored receipt at `GET /api/v1/receipt/{receiptId}`. +4. Run later verification at `POST /api/v1/receipt/{receiptId}/verify`. +5. Use authorized lifecycle actions such as revocation and provenance-state retrieval where needed. + +Canonical contract and payload examples live in [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) and the [`examples/`](../../examples) directory. diff --git a/docs/partner-eval/sample-request.json b/docs/partner-eval/sample-request.json new file mode 100644 index 0000000..2e446f0 --- /dev/null +++ b/docs/partner-eval/sample-request.json @@ -0,0 +1,22 @@ +{ + "bundleId": "verification-2026-03-12-001", + "transactionType": "deed_transfer", + "ron": { + "provider": "source-system", + "notaryId": "NOTARY-EXAMPLE-01", + "commissionState": "IL", + "sealPayload": "simulated-seal-payload" + }, + "doc": { + "docHash": "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" + }, + "policy": { + "profile": "CONTROL_CC_001" + }, + "property": { + "parcelId": "PARCEL-EXAMPLE-1001", + "county": "Cook", + "state": "IL" + }, + "timestamp": "2026-03-12T15:24:00.000Z" +} diff --git a/docs/partner-eval/sample-response.json b/docs/partner-eval/sample-response.json new file mode 100644 index 0000000..83eba8a --- /dev/null +++ b/docs/partner-eval/sample-response.json @@ -0,0 +1,22 @@ +{ + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": [ + "receipt issued" + ], + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" + }, + "revocation": { + "status": "ACTIVE" + } +} diff --git a/docs/partner-eval/security-summary.md b/docs/partner-eval/security-summary.md new file mode 100644 index 0000000..c8015e4 --- /dev/null +++ b/docs/partner-eval/security-summary.md @@ -0,0 +1,31 @@ +# Partner Security Summary + +## Problem + +Partners need enough security detail to evaluate the integration boundary without exposing internal implementation details that are not required for integration. + +## Integrity Model + +TrustSignal provides a public API boundary that is centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. + +## Integration Fit + +For the public `/api/v1/*` surface in this repository: + +- `x-api-key` authentication is required for partner operations. +- Keys are scope-bound to actions such as `verify`, `read`, `anchor`, and `revoke`. +- Request validation, rate limiting, and structured service logging are implemented at the API gateway. +- Receipt revocation requires additional issuer authorization headers. + +## Technical Detail + +TrustSignal does not require partners to understand internal proof systems, internal service topology, or signing infrastructure details in order to integrate. + +For public evaluation, the important security properties are: + +- signed verification receipts can be stored and checked later +- later verification returns current integrity and lifecycle status +- verifiable provenance metadata can be retrieved where enabled +- authorization boundaries are explicit at the route level + +Operational deployment details such as environment-specific key custody, hosting controls, and external provider posture remain infrastructure concerns outside the public integration contract. diff --git a/docs/security-summary.md b/docs/security-summary.md new file mode 100644 index 0000000..4601b76 --- /dev/null +++ b/docs/security-summary.md @@ -0,0 +1,39 @@ +# TrustSignal Public Security Summary + +## Problem + +Partners and evaluators need a public-safe summary of the TrustSignal security boundary without exposing internal implementation details that are not required for integration. + +## Integrity Model + +TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflows. + +## Integration Fit + +For the public `/api/v1/*` surface in this repository: + +- `x-api-key` authentication is required for partner operations +- keys are scope-bound to `verify`, `read`, `anchor`, and `revoke` +- request validation and rate limiting are enforced at the API boundary +- receipt revocation requires additional issuer authorization headers +- later verification is available through a dedicated receipt verification route + +## Technical Detail + +TrustSignal public materials should be understood within this boundary: + +TrustSignal provides: + +- signed verification receipts +- verification signals +- verifiable provenance metadata +- later verification capability +- an integrity layer for existing workflows + +TrustSignal does not provide: + +- legal determinations +- compliance certification +- fraud adjudication +- a replacement for the system of record +- infrastructure claims that depend on deployment-specific evidence outside this repository diff --git a/examples/verification-receipt.json b/examples/verification-receipt.json new file mode 100644 index 0000000..857510c --- /dev/null +++ b/examples/verification-receipt.json @@ -0,0 +1,48 @@ +{ + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": [ + "receipt issued" + ], + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" + }, + "revocation": { + "status": "ACTIVE" + }, + "receipt": { + "receiptVersion": "2.0", + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "createdAt": "2026-03-12T15:24:01.000Z", + "policyProfile": "CONTROL_CC_001", + "inputsCommitment": "0x2dded9c1b5c4c6d91df58a1b1793cb527f2b0cf5ddaf447f5b7d9839f7ab7d01", + "checks": [ + { + "checkId": "registry.status", + "status": "PASS", + "details": "Source responded with a current record" + } + ], + "decision": "ALLOW", + "reasons": [ + "receipt issued" + ], + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + } + }, + "canonicalReceipt": "{\"checks\":[{\"checkId\":\"registry.status\",\"details\":\"Source responded with a current record\",\"status\":\"PASS\"}],\"createdAt\":\"2026-03-12T15:24:01.000Z\",\"decision\":\"ALLOW\",\"inputsCommitment\":\"0x2dded9c1b5c4c6d91df58a1b1793cb527f2b0cf5ddaf447f5b7d9839f7ab7d01\",\"policyProfile\":\"CONTROL_CC_001\",\"reasons\":[\"receipt issued\"],\"receiptId\":\"2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a\",\"receiptVersion\":\"2.0\"}", + "pdfUrl": "/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a/pdf" +} diff --git a/examples/verification-request.json b/examples/verification-request.json new file mode 100644 index 0000000..2e446f0 --- /dev/null +++ b/examples/verification-request.json @@ -0,0 +1,22 @@ +{ + "bundleId": "verification-2026-03-12-001", + "transactionType": "deed_transfer", + "ron": { + "provider": "source-system", + "notaryId": "NOTARY-EXAMPLE-01", + "commissionState": "IL", + "sealPayload": "simulated-seal-payload" + }, + "doc": { + "docHash": "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" + }, + "policy": { + "profile": "CONTROL_CC_001" + }, + "property": { + "parcelId": "PARCEL-EXAMPLE-1001", + "county": "Cook", + "state": "IL" + }, + "timestamp": "2026-03-12T15:24:00.000Z" +} diff --git a/examples/verification-response.json b/examples/verification-response.json new file mode 100644 index 0000000..83eba8a --- /dev/null +++ b/examples/verification-response.json @@ -0,0 +1,22 @@ +{ + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": [ + "receipt issued" + ], + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" + }, + "revocation": { + "status": "ACTIVE" + } +} diff --git a/examples/verification-status.json b/examples/verification-status.json new file mode 100644 index 0000000..e7dd9a9 --- /dev/null +++ b/examples/verification-status.json @@ -0,0 +1,15 @@ +{ + "verified": true, + "integrityVerified": true, + "signatureVerified": true, + "signatureStatus": "verified", + "proofVerified": false, + "recomputedHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "storedHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "inputsCommitment": "0x2dded9c1b5c4c6d91df58a1b1793cb527f2b0cf5ddaf447f5b7d9839f7ab7d01", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current" + }, + "revoked": false +} diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..e466577 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,725 @@ +openapi: 3.0.3 +info: + title: TrustSignal Public Verification API + version: 1.0.0 + description: | + TrustSignal is evidence integrity infrastructure for existing workflows. + This contract documents the public verification lifecycle for creating signed verification receipts, + retrieving receipt state, checking later verification status, and managing authorized lifecycle actions. +servers: + - url: https://api.trustsignal.dev + description: Production +security: + - ApiKeyAuth: [] +tags: + - name: Verification + description: Create signed verification receipts and return verification signals. + - name: Receipts + description: Retrieve stored receipts and receipt-ready artifacts. + - name: Lifecycle + description: Check later verification status and manage authorized receipt lifecycle actions. +paths: + /api/v1/verify: + post: + tags: [Verification] + summary: Create a verification and receive a signed verification receipt + description: | + Submit a verification request from an existing workflow. TrustSignal returns verification signals, + a signed verification receipt, and verifiable provenance metadata that can be used for later verification. + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationRequest' + examples: + default: + summary: Verification request + value: + bundleId: verification-2026-03-12-001 + transactionType: deed_transfer + ron: + provider: source-system + notaryId: NOTARY-EXAMPLE-01 + commissionState: IL + sealPayload: simulated-seal-payload + doc: + docHash: "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" + policy: + profile: CONTROL_CC_001 + property: + parcelId: PARCEL-EXAMPLE-1001 + county: Cook + state: IL + timestamp: "2026-03-12T15:24:00.000Z" + responses: + '200': + description: Verification completed and a signed verification receipt was issued. + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationResponse' + examples: + default: + summary: Verification response + value: + receiptVersion: '2.0' + decision: ALLOW + reasons: + - receipt issued + receiptId: 2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a + receiptHash: "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04" + receiptSignature: + alg: EdDSA + kid: trustsignal-current + signature: eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ + anchor: + status: PENDING + subjectDigest: "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112" + subjectVersion: trustsignal.anchor_subject.v1 + revocation: + status: ACTIVE + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/receipt/{receiptId}: + get: + tags: [Receipts] + summary: Retrieve a stored verification receipt + description: | + Return the stored receipt view for a previously created verification, + including receipt metadata, the canonical receipt payload, and a PDF URL. + security: + - ApiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/ReceiptId' + responses: + '200': + description: Stored receipt returned. + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationReceipt' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/receipt/{receiptId}/pdf: + get: + tags: [Receipts] + summary: Download a PDF rendering of a stored verification receipt + security: + - ApiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/ReceiptId' + responses: + '200': + description: PDF returned. + content: + application/pdf: + schema: + type: string + format: binary + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/receipt/{receiptId}/verify: + post: + tags: [Lifecycle] + summary: Check later verification status for a stored receipt + description: | + Recompute receipt integrity and return the current verification status for later verification. + This endpoint does not accept a request body. + security: + - ApiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/ReceiptId' + responses: + '200': + description: Receipt verification status returned. + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationStatus' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/receipt/{receiptId}/revoke: + post: + tags: [Lifecycle] + summary: Revoke a receipt when the caller is authorized + description: | + Mark a stored receipt as revoked. This endpoint does not accept a request body. + In addition to the API key, issuer authorization headers are required. + security: + - ApiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/ReceiptId' + - $ref: '#/components/parameters/IssuerId' + - $ref: '#/components/parameters/SignatureTimestamp' + - $ref: '#/components/parameters/IssuerSignature' + responses: + '200': + description: Receipt revocation state returned. + content: + application/json: + schema: + $ref: '#/components/schemas/RevocationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/anchor/{receiptId}: + post: + tags: [Lifecycle] + summary: Record verifiable provenance metadata for a receipt when enabled + description: | + Return the current provenance state for a receipt. This endpoint does not accept a request body. + It is intended for workflows that use later verification with anchor subject metadata. + security: + - ApiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/ReceiptId' + responses: + '200': + description: Provenance state returned. + content: + application/json: + schema: + $ref: '#/components/schemas/AnchorState' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '409': + description: The receipt is not yet in a state that can expose provenance metadata. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + error: proof_artifact_required_for_anchor + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/receipts: + get: + tags: [Receipts] + summary: List recent verification receipts + description: Return a compact list of recent receipts for read-scoped integrations. + security: + - ApiKeyAuth: [] + responses: + '200': + description: Receipt list returned. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ReceiptListItem' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: x-api-key + description: | + API key authentication. TrustSignal uses scoped API keys for the public `/api/v1/*` surface. + Typical scopes are `verify`, `read`, `anchor`, and `revoke`. + parameters: + ReceiptId: + name: receiptId + in: path + required: true + description: Receipt identifier returned by `POST /api/v1/verify`. + schema: + type: string + format: uuid + IssuerId: + name: x-issuer-id + in: header + required: true + schema: + type: string + description: Authorized issuer identifier for receipt revocation. + SignatureTimestamp: + name: x-signature-timestamp + in: header + required: true + schema: + type: string + format: date-time + description: Timestamp used for issuer revocation authorization. + IssuerSignature: + name: x-issuer-signature + in: header + required: true + schema: + type: string + description: Issuer authorization signature for receipt revocation. + responses: + BadRequest: + description: Request validation failed. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Unauthorized: + description: Missing or invalid authentication material. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Forbidden: + description: The caller is authenticated but not authorized for this operation. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + NotFound: + description: Requested verification receipt was not found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + TooManyRequests: + description: Rate limit exceeded. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + ServiceUnavailable: + description: A required service dependency is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + schemas: + VerificationRequest: + type: object + additionalProperties: false + required: + - bundleId + - transactionType + - ron + - doc + - policy + - property + properties: + bundleId: + type: string + minLength: 1 + description: Caller-controlled verification identifier. + transactionType: + type: string + minLength: 1 + description: Existing workflow transaction category. + ron: + type: object + additionalProperties: false + required: + - provider + - notaryId + - commissionState + - sealPayload + properties: + provider: + type: string + notaryId: + type: string + commissionState: + type: string + minLength: 2 + maxLength: 2 + sealPayload: + type: string + sealScheme: + type: string + enum: [SIM-ECDSA-v1] + doc: + type: object + additionalProperties: false + required: + - docHash + properties: + docHash: + type: string + description: Artifact hash supplied by the caller. + pdfBase64: + type: string + description: Optional artifact payload when the integration sends the full document. + policy: + type: object + additionalProperties: false + required: + - profile + properties: + profile: + type: string + description: Policy or control identifier for the verification. + property: + type: object + additionalProperties: false + required: + - parcelId + - county + - state + properties: + parcelId: + type: string + county: + type: string + state: + type: string + minLength: 2 + maxLength: 2 + ocrData: + type: object + additionalProperties: false + properties: + notaryName: + type: string + notaryCommissionId: + type: string + propertyAddress: + type: string + grantorName: + type: string + registryScreening: + type: object + additionalProperties: false + properties: + subjectName: + type: string + minLength: 2 + maxLength: 256 + sourceIds: + type: array + items: + type: string + minItems: 1 + maxItems: 50 + forceRefresh: + type: boolean + timestamp: + type: string + format: date-time + description: Caller-provided event timestamp. + VerificationResponse: + type: object + additionalProperties: true + required: + - receiptVersion + - decision + - reasons + - receiptId + - receiptHash + - anchor + - revocation + properties: + receiptVersion: + type: string + example: '2.0' + decision: + type: string + enum: [ALLOW, FLAG, BLOCK] + description: Verification signal for the submitted request. + reasons: + type: array + items: + type: string + receiptId: + type: string + format: uuid + receiptHash: + type: string + receiptSignature: + $ref: '#/components/schemas/ReceiptSignature' + anchor: + $ref: '#/components/schemas/AnchorState' + revocation: + $ref: '#/components/schemas/RevocationState' + description: | + Public response fields for receipt issuance. Additional implementation-specific fields may also be present. + VerificationReceipt: + allOf: + - $ref: '#/components/schemas/VerificationResponse' + - type: object + additionalProperties: true + required: + - receipt + - canonicalReceipt + - pdfUrl + properties: + receipt: + $ref: '#/components/schemas/StoredReceipt' + canonicalReceipt: + type: string + description: Canonical receipt payload used for later verification. + pdfUrl: + type: string + description: Relative URL for the PDF rendering of the receipt. + StoredReceipt: + type: object + additionalProperties: true + required: + - receiptVersion + - receiptId + - createdAt + - policyProfile + - inputsCommitment + - checks + - decision + - reasons + - receiptHash + properties: + receiptVersion: + type: string + receiptId: + type: string + format: uuid + createdAt: + type: string + format: date-time + policyProfile: + type: string + inputsCommitment: + type: string + checks: + type: array + items: + $ref: '#/components/schemas/CheckResult' + decision: + type: string + enum: [ALLOW, FLAG, BLOCK] + reasons: + type: array + items: + type: string + receiptHash: + type: string + receiptSignature: + $ref: '#/components/schemas/ReceiptSignature' + description: | + Stored receipt representation returned by the gateway. Integrations should rely on the documented receipt fields + and should not infer internal engine behavior from optional fields. + VerificationStatus: + type: object + additionalProperties: false + required: + - verified + - integrityVerified + - signatureVerified + - signatureStatus + - proofVerified + - recomputedHash + - storedHash + - inputsCommitment + - receiptSignature + - revoked + properties: + verified: + type: boolean + description: Overall later verification result. + integrityVerified: + type: boolean + description: Whether the stored receipt still matches its canonical hash. + signatureVerified: + type: boolean + description: Whether the stored receipt signature verified. + signatureStatus: + type: string + enum: [verified, invalid, unknown-kid, legacy-unsigned] + signatureReason: + type: string + proofVerified: + type: boolean + description: Public verification status flag returned by the API. + recomputedHash: + type: string + storedHash: + type: string + inputsCommitment: + type: string + receiptSignature: + oneOf: + - $ref: '#/components/schemas/ReceiptSignatureSummary' + - type: 'null' + revoked: + type: boolean + AnchorState: + type: object + additionalProperties: true + required: + - status + properties: + status: + type: string + description: Current provenance state for the receipt. + anchoredAt: + type: string + format: date-time + subjectDigest: + type: string + description: Verifiable provenance digest for later verification. + subjectVersion: + type: string + description: Version label for the provenance digest format. + description: | + Public-safe provenance metadata. Implementations may return additional fields, but integrations should treat + this object as provenance state rather than infrastructure detail. + ReceiptListItem: + type: object + additionalProperties: false + required: + - receiptId + - decision + - createdAt + - anchorStatus + - revoked + properties: + receiptId: + type: string + format: uuid + decision: + type: string + enum: [ALLOW, FLAG, BLOCK] + createdAt: + type: string + format: date-time + anchorStatus: + type: string + revoked: + type: boolean + RevocationState: + type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: [ACTIVE, REVOKED, ALREADY_REVOKED] + RevocationResponse: + type: object + additionalProperties: false + required: + - status + properties: + status: + type: string + enum: [REVOKED, ALREADY_REVOKED] + issuerId: + type: string + description: Authorized issuer identifier returned when the receipt is revoked. + ReceiptSignature: + type: object + additionalProperties: false + required: + - alg + - kid + - signature + properties: + alg: + type: string + enum: [EdDSA] + kid: + type: string + signature: + type: string + description: Signed receipt artifact returned by the API. + ReceiptSignatureSummary: + type: object + additionalProperties: false + required: + - alg + - kid + properties: + alg: + type: string + enum: [EdDSA] + kid: + type: string + CheckResult: + type: object + additionalProperties: false + required: + - checkId + - status + properties: + checkId: + type: string + status: + type: string + enum: [PASS, FAIL, WARN] + details: + type: string + ErrorResponse: + type: object + additionalProperties: true + required: + - error + properties: + error: + type: string + message: + type: string + details: + description: Optional validation or request context. diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md index 0ffe458..fcf47cb 100644 --- a/wiki/API-Overview.md +++ b/wiki/API-Overview.md @@ -11,132 +11,68 @@ # API Overview -TrustSignal currently exposes two API surfaces in this repository. +## Problem -## API Families +Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. -### Integration-Facing API +## Integrity Model -The `/api/v1/*` surface is the main partner-facing integration API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. +TrustSignal exposes a public verification lifecycle centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. -### Legacy JWT API +## Integration Fit -The `/v1/*` surface is still present and is the surface used by the current JavaScript SDK. It uses bearer JWT authentication. +The integration-facing `/api/v1/*` surface is the main public partner API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. -## Request Path From the Codebase +The legacy `/v1/*` surface is still present for the current JavaScript SDK and uses bearer JWT authentication. -For the integration-facing API, the dominant request pattern is: +## Technical Detail -```mermaid -sequenceDiagram - participant C as Client - participant G as apps/api/src/server.ts - participant E as VerificationEngine - participant V as localVerificationEngine - - C->>G: HTTP request - G->>G: Auth + validation + rate limit - G->>E: Engine interface call - E->>V: Execute operation - V-->>G: Typed result - G-->>C: Shaped API response -``` - -## `/api/v1/*` Endpoints +### Integration-Facing Verification Lifecycle | Method | Path | Auth | Purpose | | --- | --- | --- | --- | -| `GET` | `/api/v1/health` | none | Basic service and database readiness snapshot | -| `GET` | `/api/v1/status` | none | Service status, environment, ingress, and database posture | -| `GET` | `/api/v1/metrics` | none | Prometheus-compatible metrics | -| `GET` | `/api/v1/integrations/vanta/schema` | `x-api-key` with `read` | Return the Vanta evidence schema | -| `GET` | `/api/v1/integrations/vanta/verification/:receiptId` | `x-api-key` with `read` | Return a normalized Vanta evidence payload | -| `GET` | `/api/v1/registry/sources` | `x-api-key` with `read` | List configured registry sources | -| `POST` | `/api/v1/registry/verify` | `x-api-key` with `verify` | Run a single registry verification request | -| `POST` | `/api/v1/registry/verify-batch` | `x-api-key` with `verify` | Run a batch registry verification request | -| `GET` | `/api/v1/registry/jobs` | `x-api-key` with `read` | List registry job records | -| `GET` | `/api/v1/registry/jobs/:jobId` | `x-api-key` with `read` | Retrieve one registry job record | -| `POST` | `/api/v1/verify/attom` | `x-api-key` with `verify` | Run the Cook County ATTOM cross-check | -| `POST` | `/api/v1/verify` | `x-api-key` with `verify` | Create a verification receipt | -| `GET` | `/api/v1/synthetic` | `x-api-key` with `read` | Return a synthetic bundle for testing | +| `POST` | `/api/v1/verify` | `x-api-key` with `verify` | Create a verification and receive a signed verification receipt | | `GET` | `/api/v1/receipt/:receiptId` | `x-api-key` with `read` | Retrieve a stored receipt | -| `GET` | `/api/v1/receipt/:receiptId/pdf` | `x-api-key` with `read` | Download a PDF receipt | -| `POST` | `/api/v1/receipt/:receiptId/verify` | `x-api-key` with `read` | Verify receipt integrity and status | -| `POST` | `/api/v1/anchor/:receiptId` | `x-api-key` with `anchor` | Trigger receipt anchoring when allowed | +| `GET` | `/api/v1/receipt/:receiptId/pdf` | `x-api-key` with `read` | Download a PDF rendering of the receipt | +| `POST` | `/api/v1/receipt/:receiptId/verify` | `x-api-key` with `read` | Run later verification for a stored receipt | | `POST` | `/api/v1/receipt/:receiptId/revoke` | `x-api-key` with `revoke` | Revoke a receipt with issuer authorization | +| `POST` | `/api/v1/anchor/:receiptId` | `x-api-key` with `anchor` | Return provenance state for a receipt when enabled | | `GET` | `/api/v1/receipts` | `x-api-key` with `read` | List recent receipts | -`POST /api/v1/receipt/:receiptId/revoke` also requires these signed issuer headers: +`POST /api/v1/receipt/:receiptId/revoke` also requires these issuer authorization headers: - `x-issuer-id` - `x-signature-timestamp` - `x-issuer-signature` -## Gateway to Engine Mapping - -These route handlers currently delegate through the engine interface: +### Additional Integration Routes -| Method | Path | Gateway Action | Engine Call | -| --- | --- | --- | --- | -| `GET` | `/api/v1/integrations/vanta/verification/:receiptId` | parse `receiptId`, validate auth, shape schema response | `getVantaVerificationResult` | -| `GET` | `/api/v1/registry/sources` | validate auth | `listRegistrySources` | -| `POST` | `/api/v1/registry/verify` | validate body, auth, and source id | `verifyRegistrySource` | -| `POST` | `/api/v1/registry/verify-batch` | validate body, auth, and source ids | `verifyRegistrySources` | -| `GET` | `/api/v1/registry/jobs` | parse limit, validate auth | `listRegistryOracleJobs` | -| `GET` | `/api/v1/registry/jobs/:jobId` | validate auth and route param | `getRegistryOracleJob` | -| `POST` | `/api/v1/verify/attom` | validate deed payload and county guard | `crossCheckAttom` | -| `POST` | `/api/v1/verify` | validate verification payload and map response | `createVerification` | -| `GET` | `/api/v1/synthetic` | validate auth | `createSyntheticBundle` | -| `GET` | `/api/v1/receipt/:receiptId` | parse `receiptId` and map response | `getReceipt` | -| `GET` | `/api/v1/receipt/:receiptId/pdf` | parse `receiptId`, then render PDF from stored receipt | `getReceipt` | -| `POST` | `/api/v1/receipt/:receiptId/verify` | reject body, parse `receiptId` | `getVerificationStatus` | -| `POST` | `/api/v1/anchor/:receiptId` | reject body, parse `receiptId` | `anchorReceipt` | -| `POST` | `/api/v1/receipt/:receiptId/revoke` | reject body, parse `receiptId`, verify issuer headers | `revokeReceipt` | - -Gateway-owned routes that do not call the engine interface directly: - -| Method | Path | Notes | +| Method | Path | Purpose | | --- | --- | --- | -| `GET` | `/api/v1/health` | service health and database readiness | -| `GET` | `/api/v1/status` | service status and deployment posture snapshot | -| `GET` | `/api/v1/metrics` | Prometheus metrics | -| `GET` | `/api/v1/integrations/vanta/schema` | static schema response | -| `GET` | `/api/v1/receipts` | recent receipt listing view | +| `GET` | `/api/v1/health` | Service health snapshot | +| `GET` | `/api/v1/status` | Deployment status snapshot | +| `GET` | `/api/v1/metrics` | Prometheus-compatible metrics | +| `GET` | `/api/v1/integrations/vanta/schema` | Vanta schema metadata | +| `GET` | `/api/v1/integrations/vanta/verification/:receiptId` | Normalized verification payload for Vanta workflows | +| `POST` | `/api/v1/verify/attom` | Cook County ATTOM cross-check | -## `/v1/*` Endpoints +### Legacy JWT Surface | Method | Path | Auth | Purpose | | --- | --- | --- | --- | -| `POST` | `/v1/verify-bundle` | bearer JWT | Verify a bundle and return the combined result | -| `POST` | `/v1/revoke` | bearer JWT with admin authorization | Revoke a bundle | +| `POST` | `/v1/verify-bundle` | bearer JWT | Verify a bundle | | `GET` | `/v1/status/:bundleId` | bearer JWT | Check bundle status | +| `POST` | `/v1/revoke` | bearer JWT with admin authorization | Revoke a bundle | -## Legacy `/v1/*` Route Behavior - -The `/v1/*` handlers are still present in `src/routes/` and currently use older route dependencies rather than the `apps/api` engine interface. - -| Method | Path | Current Handler Flow | -| --- | --- | --- | -| `POST` | `/v1/verify-bundle` | validate body -> `deps.verifyBundle(...)` -> `recordStore.create(...)` | -| `GET` | `/v1/status/:bundleId` | validate param -> `recordStore.findByBundleHash(...)` | -| `POST` | `/v1/revoke` | validate body and admin claim -> `recordStore.findByBundleHash(...)` -> `anchorNullifier(...)` -> `recordStore.revokeByBundleHash(...)` | - -## Error Semantics +### Error Semantics -Integrators should expect these broad response patterns: +Integrators should expect these broad patterns: -- `400` for schema or request-shape errors +- `400` for request-shape errors - `401` or `403` for missing credentials, invalid credentials, or missing scope -- `404` for unknown receipts or jobs -- `409` for lifecycle conflicts such as missing preconditions -- `429` for rate limits -- `502` for upstream dependency failures -- `503` when the service is up but a required database path is unavailable - -## Integration Notes - -- Use `/api/v1/*` for receipt-oriented integrations and partner workflows. -- Use `/v1/*` only if you are integrating with the current SDK or an existing JWT-based bundle flow. -- Treat the response payload as the source of technical verification status. Do not infer state from transport success alone. -- The `/api/v1/*` surface is the one that currently matches the public gateway and private engine boundary described elsewhere in this wiki. -- For a concrete payload and response example, see [Quick Verification Example](Quick-Verification-Example). +- `404` for unknown receipts +- `409` for lifecycle conflicts +- `429` for rate limiting +- `503` when a required dependency is unavailable + +The canonical public contract for the verification lifecycle is [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml). diff --git a/wiki/Claims-Boundary.md b/wiki/Claims-Boundary.md index 7c3c102..9f33d66 100644 --- a/wiki/Claims-Boundary.md +++ b/wiki/Claims-Boundary.md @@ -11,26 +11,32 @@ # Claims Boundary -## What TrustSignal Provides +## Problem -TrustSignal is evidence integrity infrastructure for compliance artifacts. It provides: +Public integrations need a clear technical boundary so partner engineers and reviewers know what the TrustSignal response means and what it does not mean. + +## Integrity Model + +TrustSignal is evidence integrity infrastructure. It acts as an integrity layer for existing workflows and provides: - signed verification receipts - verification signals -- receipt lifecycle state -- verifiable provenance for artifacts -- API-accessible verification results +- verifiable provenance metadata +- later verification capability +- API-accessible receipt lifecycle state + +## Integration Fit -## What TrustSignal Does Not Provide +TrustSignal is designed to sit behind an upstream workflow that remains the system of record. The partner or workflow owner keeps control of collection, review, and business decisions. + +## Technical Detail TrustSignal does not provide: - legal determinations - compliance certification - fraud adjudication -- replacement for system-of-record workflows -- guarantees that depend on private infrastructure evidence - -## Why This Boundary Exists +- a replacement for system-of-record workflows +- guarantees that depend on environment-specific infrastructure evidence outside this repository -TrustSignal operates as an integrity layer within existing workflows. It produces verification artifacts that downstream systems can use for audit, investigation, or compliance evidence validation, while preserving workflow ownership in the upstream system of record. +The TrustSignal response should be treated as a technical verification artifact that supports audit-ready evidence and later verification. diff --git a/wiki/Home.md b/wiki/Home.md index 1f8253e..e30dde5 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -11,56 +11,16 @@ # TrustSignal Wiki -TrustSignal provides signed receipts and verifiable provenance for compliance artifacts. It is an integrity layer for existing workflows, not a replacement for the system of record. - -This wiki is written for engineers, technical evaluators, and partner reviewers who need to understand what TrustSignal does, where it fits, and how to integrate with it without exposing private verification-engine details. +TrustSignal is evidence integrity infrastructure for existing workflows. It acts as an integrity layer that provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability. ## Start Here - [What is TrustSignal](What-is-TrustSignal) -- [Evidence Integrity Architecture](Evidence-Integrity-Architecture) -- [Verification Receipts](Verification-Receipts) - [API Overview](API-Overview) +- [Verification Receipts](Verification-Receipts) - [Claims Boundary](Claims-Boundary) - [Quick Verification Example](Quick-Verification-Example) -- [SDK Usage](SDK-Usage) -- [Vanta Integration Example](Vanta-Integration-Example) -- [Security Model](Security-Model) -- [Threat Model](Threat-Model) -- [FAQ](FAQ) - -## Documentation Scope - -This wiki covers: - -- product positioning and integration model -- public API surfaces -- receipt lifecycle behavior -- SDK usage -- partner-facing security expectations -- threat-model framing for external reviewers - -This wiki does not document: - -- proof internals -- model internals -- private scoring logic -- signing infrastructure implementation details -- internal service topology - -## Key Ideas - -- TrustSignal accepts a verification request from an existing workflow. -- TrustSignal returns a signed verification receipt with stable identifiers. -- That receipt can be retrieved, checked, and attached to downstream audit or compliance workflows. -- TrustSignal can also produce normalized evidence payloads for systems such as Vanta. - -If you want to see the smallest end-to-end payload example first, start with [Quick Verification Example](Quick-Verification-Example). - -## Website - -- https://trustsignal.dev -## Claims Boundary +## Current Boundary -TrustSignal provides technical verification signals, not legal determinations. Public-facing descriptions should avoid claiming completed compliance certification, completed production hardening in every environment, or guarantees that depend on private infrastructure evidence. +TrustSignal provides technical verification artifacts. It does not provide legal determinations, compliance certification, fraud adjudication, or a replacement for the upstream system of record. diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md index 0b05dc2..780cf84 100644 --- a/wiki/Quick-Verification-Example.md +++ b/wiki/Quick-Verification-Example.md @@ -11,20 +11,15 @@ # Quick Verification Example -This page is written for developers, integration engineers, compliance engineers, and technical partner reviewers who want to understand the smallest realistic TrustSignal verification flow. +## Problem -## What This Example Demonstrates +This example is for partner engineers who want the smallest realistic TrustSignal flow that shows what goes in, what comes back, and how later verification works. -This example shows how to: +## Integrity Model -- submit a verification request to the public TrustSignal API -- receive a decision plus a signed receipt -- inspect the main receipt fields returned by the API -- understand how later re-verification works +The example uses the current integration-facing lifecycle to create a verification, return verification signals plus a signed verification receipt, and later verify stored receipt state. -The example uses the current integration-facing receipt workflow on `POST /api/v1/verify`. - -## Verification Lifecycle +## Integration Fit ```mermaid sequenceDiagram @@ -34,22 +29,21 @@ sequenceDiagram C->>A: POST /api/v1/verify A->>E: createVerification(...) - E-->>A: verification result + receipt + E-->>A: verification result + signed verification receipt A-->>C: JSON response C->>A: POST /api/v1/receipt/:receiptId/verify A->>E: getVerificationStatus(...) E-->>A: current receipt status - A-->>C: integrity and lifecycle status + A-->>C: later verification response ``` -## Product Terms and Current API Fields +## Technical Detail -The public product language often maps to the current API contract like this: +### Product Terms And Current API Fields | Product Term | Current API Field | | --- | --- | | `artifact_hash` | `doc.docHash` | -| `source` | caller-owned workflow or integration context | | `timestamp` | `timestamp` | | `control_id` | `policy.profile` | | `verification_id` | `bundleId` | @@ -58,49 +52,22 @@ The public product language often maps to the current API contract like this: | `status` | `decision` and later verification status | | `anchor_subject_digest` | `anchor.subjectDigest` | -## Example Request +### Example Request ```bash curl -X POST https://api.trustsignal.example/api/v1/verify \ -H "Content-Type: application/json" \ -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ - -d '{ - "bundleId": "verification-2026-04-18-001", - "transactionType": "compliance_evidence", - "ron": { - "provider": "source-system", - "notaryId": "NOTARY-EXAMPLE-01", - "commissionState": "IL", - "sealPayload": "example-seal-payload" - }, - "doc": { - "docHash": "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" - }, - "property": { - "parcelId": "PARCEL-EXAMPLE-1001", - "county": "Cook", - "state": "IL" - }, - "policy": { - "profile": "CONTROL_CC_001" - }, - "timestamp": "2026-04-18T15:24:00.000Z" - }' + -d @examples/verification-request.json ``` -This example uses: - -- `bundleId` as the caller-controlled verification identifier -- `doc.docHash` as the artifact hash -- `policy.profile` as the control or policy context - -## Example Response +### Example Response ```json { "receiptVersion": "2.0", "decision": "ALLOW", - "reasons": [], + "reasons": ["receipt issued"], "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", "receiptSignature": { @@ -110,7 +77,8 @@ This example uses: }, "anchor": { "status": "PENDING", - "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112" + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" }, "revocation": { "status": "ACTIVE" @@ -118,49 +86,30 @@ This example uses: } ``` -## Key Response Fields +### Later Verification -- `receiptId`: the durable receipt handle for later retrieval and verification -- `receiptHash`: the integrity digest for the returned receipt payload -- `receiptSignature`: the presence of a signed receipt artifact, without exposing signing infrastructure details -- `anchor.subjectDigest`: the public-facing anchor subject digest when available -- `decision`: the current verification outcome returned by the public API -- `revocation.status`: whether the receipt is currently active or revoked +To retrieve the stored receipt: -## Later Re-Verification - -To check the receipt later, call: - -- `GET /api/v1/receipt/:receiptId` to retrieve the stored receipt -- `POST /api/v1/receipt/:receiptId/verify` to validate current receipt integrity and status +```bash +curl -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + https://api.trustsignal.example/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a +``` -Example: +To run later verification: ```bash -curl -X POST https://api.trustsignal.example/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a/verify \ - -H "x-api-key: $TRUSTSIGNAL_API_KEY" +curl -X POST -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + https://api.trustsignal.example/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a/verify ``` -That later check is how downstream systems confirm that the receipt still matches stored verification state rather than relying only on the original response. - -## What This Does Not Expose +### What This Does Not Expose -This example intentionally does not expose: +This public example does not expose: -- proof witness details -- scoring internals +- proof internals - circuit identifiers - model outputs - signing infrastructure specifics - internal service topology - -The public integration contract is the request, the response, and the receipt lifecycle behavior. - -## Production Readiness - -For production integrations, treat the request/response example as the contract and operationalize these controls: - -- authentication and API keys with scoped access per workflow -- environment configuration for required service dependencies and secure configuration loading -- receipt lifecycle monitoring for new, verified, revoked, and anchored states -- verification checks before relying on earlier results in audit or partner handoff workflows +- witness or prover details +- registry scoring algorithms diff --git a/wiki/Verification-Receipts.md b/wiki/Verification-Receipts.md index a38f6ea..7f6c270 100644 --- a/wiki/Verification-Receipts.md +++ b/wiki/Verification-Receipts.md @@ -11,84 +11,51 @@ # Verification Receipts -A TrustSignal verification receipt is the durable output of a verification event. It provides a stable identifier, a signed payload, and lifecycle state that downstream systems can use as audit evidence. +## Problem -## Receipt Lifecycle +A workflow record alone is often not enough when a team needs to verify later what artifact was evaluated and what result was returned. + +## Integrity Model + +A TrustSignal verification receipt is the durable output of a verification event. It provides a stable identifier, a signed verification artifact, verification signals, and lifecycle state that downstream systems can use for audit-ready evidence and later verification. + +## Integration Fit ```mermaid flowchart LR A[Verification Request] --> B[POST /api/v1/verify] - B --> C[Signed Receipt] + B --> C[Signed Verification Receipt] C --> D[GET /api/v1/receipt/:receiptId] C --> E[POST /api/v1/receipt/:receiptId/verify] C --> F[POST /api/v1/receipt/:receiptId/revoke] C --> G[POST /api/v1/anchor/:receiptId] ``` -## Receipt Verification Flow - -```mermaid -sequenceDiagram - participant C as Client - participant G as API Gateway - participant E as Engine Interface - participant V as Verification Engine - participant S as Stored Receipt - - C->>G: POST /api/v1/receipt/:receiptId/verify - G->>G: Reject unexpected body and parse receiptId - G->>E: getVerificationStatus(receiptId) - E->>V: Load receipt and verify stored state - V->>S: Read stored receipt record - V-->>G: Verification status - G-->>C: Integrity and signature status response -``` +## Technical Detail -## Core Receipt Concepts +### Core Receipt Concepts | Field | Purpose | | --- | --- | | `receiptId` | Stable identifier for retrieval and lifecycle operations | | `receiptHash` | Canonical digest of the unsigned receipt payload | | `inputsCommitment` | Digest representing the verification input bundle | -| `decision` | High-level outcome such as `ALLOW`, `FLAG`, or `BLOCK` | +| `decision` | High-level verification signal such as `ALLOW`, `FLAG`, or `BLOCK` | | `checks` | Individual check results included in the receipt payload | | `reasons` | Human-readable reasons associated with the decision | -| `receiptSignature` | Signature metadata used to verify the receipt payload | -| `revocation.status` | Whether the receipt is still active | -| `anchor.status` | Whether anchoring has occurred for the receipt | +| `receiptSignature` | Signed verification receipt material returned by the API | +| `revocation.status` | Whether the receipt is active or revoked | +| `anchor.subjectDigest` | Verifiable provenance digest used for later verification | -## Typical Operations +### Typical Operations - Create a receipt with `POST /api/v1/verify` - Retrieve the stored receipt with `GET /api/v1/receipt/:receiptId` - Download a PDF rendering with `GET /api/v1/receipt/:receiptId/pdf` -- Re-check integrity and signature status with `POST /api/v1/receipt/:receiptId/verify` +- Run later verification with `POST /api/v1/receipt/:receiptId/verify` - Revoke a receipt when authorized with `POST /api/v1/receipt/:receiptId/revoke` -- Trigger anchoring when enabled with `POST /api/v1/anchor/:receiptId` - -For a concrete payload example, see [Quick Verification Example](Quick-Verification-Example). - -## Route-Derived Receipt Paths - -The receipt lifecycle implemented in `apps/api/src/server.ts` currently follows this pattern: - -- `GET /api/v1/receipt/:receiptId` calls `getReceipt` -- `GET /api/v1/receipt/:receiptId/pdf` calls `getReceipt`, then renders PDF in the gateway -- `POST /api/v1/receipt/:receiptId/verify` calls `getVerificationStatus` -- `POST /api/v1/anchor/:receiptId` calls `anchorReceipt` -- `POST /api/v1/receipt/:receiptId/revoke` verifies issuer headers, then calls `revokeReceipt` - -## Why Receipts Matter - -The receipt is the bridge between a one-time verification event and later audit use. Instead of relying on screenshots, operator notes, or implicit state in another system, a downstream reviewer can work from a signed artifact with an explicit lifecycle. - -## Practical Guidance - -- Store the `receiptId` and `receiptHash` in the upstream workflow record. -- Treat the receipt as evidence of what TrustSignal evaluated at that point in time. -- Re-run receipt verification before audit submission or partner handoff when current status matters. +- Retrieve provenance state when enabled with `POST /api/v1/anchor/:receiptId` -## Claims Boundary +### Claims Boundary -A receipt is a technical verification artifact. It is not a legal determination, and it does not replace the underlying business process or system-of-record controls. +A receipt is a technical verification artifact. It is not a legal determination, compliance certification, fraud adjudication, or a replacement for the system of record. diff --git a/wiki/What-is-TrustSignal.md b/wiki/What-is-TrustSignal.md index f1bdc56..1a670e3 100644 --- a/wiki/What-is-TrustSignal.md +++ b/wiki/What-is-TrustSignal.md @@ -11,18 +11,15 @@ # What Is TrustSignal -TrustSignal is evidence integrity infrastructure for compliance artifacts. It issues signed verification receipts and preserves the information needed to later confirm that a verification result still corresponds to the artifact and policy context originally evaluated. +## Problem -## What It Does +Many workflow systems can show that an artifact was collected or reviewed. Fewer can later verify that the same artifact is still the one tied to the recorded decision. -TrustSignal helps teams: +## Integrity Model -- create signed receipts when evidence is evaluated -- retrieve those receipts later for audit, review, or partner workflows -- re-verify stored receipts without depending on screenshots or manual notes -- export normalized evidence payloads for downstream systems +TrustSignal is evidence integrity infrastructure. It provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification for existing workflows. -## Where It Fits +## Integration Fit TrustSignal fits behind an existing platform such as: @@ -31,35 +28,26 @@ TrustSignal fits behind an existing platform such as: - a partner portal - a vertical workflow such as deed verification -The upstream platform remains the system of record. TrustSignal adds integrity evidence at the boundary. +The upstream platform remains the system of record. TrustSignal adds an integrity layer at the workflow boundary. -## Verification Model +## Technical Detail -At a product level, the model is straightforward: +At a high level, the public verification lifecycle is: -1. An upstream system submits a verification request or artifact reference. -2. TrustSignal evaluates the request against the configured policy and data dependencies. -3. TrustSignal returns a decision plus a signed verification receipt. -4. Downstream systems use the receipt as a stable audit artifact. -5. Later checks can confirm receipt integrity, status, and lifecycle state. +1. An upstream system submits a verification request. +2. TrustSignal evaluates the request against configured checks. +3. TrustSignal returns verification signals and a signed verification receipt. +4. Downstream systems store the receipt with the workflow record. +5. Later verification confirms receipt integrity, status, and provenance state when needed. -In the current codebase, the integration-facing `/api/v1/*` routes implement that model by validating requests in the gateway and delegating the major lifecycle actions to the engine interface. +In the current codebase, the integration-facing `/api/v1/*` routes implement that lifecycle. The legacy `/v1/*` surface remains present for the current SDK. ## What TrustSignal Is Not TrustSignal is not: -- a replacement for compliance workflow software +- a replacement for workflow software - a legal decision engine -- a guarantee that an upstream source system is correct -- a substitute for environment-specific security evidence or control validation - -## Current Repository Context - -This repository currently exposes: - -- the integration-facing `/api/v1/*` API surface -- the legacy `/v1/*` API surface used by the JavaScript SDK -- the DeedShield application module as the current product surface in-repo - -The product framing remains broader than a single module: TrustSignal is the integrity layer that sits behind workflow-specific applications. +- a compliance certification service +- a fraud adjudication service +- a substitute for environment-specific security evidence From a201995557b2632b66b173467a69dbf6628f1f82 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 16:13:38 -0500 Subject: [PATCH 03/25] feat: add evaluator quickstart and Postman trial path --- README.md | 19 ++ docs/README.md | 21 +- docs/partner-eval/api-playground.md | 120 +++++++++++ docs/partner-eval/overview.md | 7 + docs/partner-eval/quickstart.md | 101 ++++++++++ ...TrustSignal.local.postman_environment.json | 45 +++++ postman/TrustSignal.postman_collection.json | 186 ++++++++++++++++++ wiki/API-Overview.md | 16 ++ wiki/Quick-Verification-Example.md | 7 + 9 files changed, 520 insertions(+), 2 deletions(-) create mode 100644 docs/partner-eval/api-playground.md create mode 100644 docs/partner-eval/quickstart.md create mode 100644 postman/TrustSignal.local.postman_environment.json create mode 100644 postman/TrustSignal.postman_collection.json diff --git a/README.md b/README.md index 3518c69..9d70fbf 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,23 @@ The repository also still includes a legacy JWT-authenticated `/v1/*` surface us - `GET /v1/status/:bundleId` - `POST /v1/revoke` +## Evaluate The API + +Start here if you are evaluating the public verification lifecycle: + +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +- [Postman local environment](/Users/christopher/Projects/trustsignal/postman/TrustSignal.local.postman_environment.json) + +Golden path: + +1. submit a verification request +2. receive verification signals plus a signed verification receipt +3. retrieve the stored receipt +4. run later verification + ## Public API Contract And Examples The public evaluation artifacts added in this repo are: @@ -141,6 +158,8 @@ npm run build ## Documentation Map - [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [docs/partner-eval/quickstart.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [docs/partner-eval/api-playground.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) - [wiki/What-is-TrustSignal.md](/Users/christopher/Projects/trustsignal/wiki/What-is-TrustSignal.md) - [wiki/API-Overview.md](/Users/christopher/Projects/trustsignal/wiki/API-Overview.md) - [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/README.md b/docs/README.md index be13d02..dbc790b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,24 @@ This folder is organized into active, canonical documents and archived historical material. +## Evaluator Quickstart + +Start here if you want to evaluate the public verification lifecycle quickly: + +- [Partner evaluation overview](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +- [Postman local environment](/Users/christopher/Projects/trustsignal/postman/TrustSignal.local.postman_environment.json) + +Golden path: + +1. submit a verification request +2. receive verification signals plus a signed verification receipt +3. retrieve the stored receipt +4. run later verification + ## Canonical Documentation - `final/01_EXECUTIVE_SUMMARY.md` - `final/02_ARCHITECTURE_AND_BOUNDARIES.md` @@ -21,6 +39,7 @@ This folder is organized into active, canonical documents and archived historica ## Governance and Security Tracking - `PRODUCTION_GOVERNANCE_TRACKER.md` - `SECURITY.md` +- `security-summary.md` - `verification.md` - `ops/monitoring/README.md` - `../PROJECT_PLAN.md` @@ -32,8 +51,6 @@ This folder is organized into active, canonical documents and archived historica - `legal/cookie-policy.md` - `legal/pilot-agreement.md` -## Forms - ## Archive Historical planning, synthesized source-of-truth drafts, and early notebook logs are retained under: - `archive/legacy-2026-02-25/` diff --git a/docs/partner-eval/api-playground.md b/docs/partner-eval/api-playground.md new file mode 100644 index 0000000..cc561ef --- /dev/null +++ b/docs/partner-eval/api-playground.md @@ -0,0 +1,120 @@ +# TrustSignal API Playground + +## Problem + +Evaluators often want a single page that shows the public API lifecycle, the exact artifacts to use, and the smallest realistic set of commands for testing. + +## Integrity Model + +TrustSignal provides verification signals, signed verification receipts, verifiable provenance metadata, and later verification through the public `/api/v1/*` surface. + +## Integration Fit + +Use this playground when you want to test the golden path quickly: + +1. submit a verification request +2. receive verification signals plus a signed verification receipt +3. retrieve the stored receipt +4. run later verification + +Canonical assets: + +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) +- [verification-response.json](/Users/christopher/Projects/trustsignal/examples/verification-response.json) +- [verification-receipt.json](/Users/christopher/Projects/trustsignal/examples/verification-receipt.json) +- [verification-status.json](/Users/christopher/Projects/trustsignal/examples/verification-status.json) +- [TrustSignal.postman_collection.json](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) + +## Technical Detail + +### Environment + +```bash +export TRUSTSIGNAL_BASE_URL="http://localhost:3001" +export TRUSTSIGNAL_API_KEY="replace-with-api-key" +export RECEIPT_ID="replace-after-verify" +``` + +### Golden Path Diagram + +```mermaid +flowchart LR + A[Verification Request] --> B[POST /api/v1/verify] + B --> C[Verification Signals + Signed Verification Receipt] + C --> D[GET /api/v1/receipt/{receiptId}] + C --> E[POST /api/v1/receipt/{receiptId}/verify] +``` + +### Verify + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/verify" \ + -H "Content-Type: application/json" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + --data @examples/verification-request.json +``` + +Expected response example: + +```json +{ + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": ["receipt issued"], + "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" + }, + "revocation": { + "status": "ACTIVE" + } +} +``` + +### Retrieve The Receipt + +```bash +curl "$TRUSTSIGNAL_BASE_URL/api/v1/receipt/$RECEIPT_ID" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +### Run Later Verification + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/receipt/$RECEIPT_ID/verify" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +### Review Revocation Or Provenance State + +If your evaluation includes lifecycle review: + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/anchor/$RECEIPT_ID" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +Revocation is also public, but it requires issuer authorization headers in addition to the API key. Use the Postman collection if you want the full request template. + +## Evaluator Notes + +Focus the evaluation on: + +- whether the API returns verification signals you can store in an existing workflow +- whether signed verification receipts are easy to retrieve later +- whether later verification is explicit and easy to re-run +- whether the public contract exposes verifiable provenance without exposing internal implementation details + +## Claims Boundary + +This playground is for public API evaluation. It does not claim legal determinations, compliance certification, fraud adjudication, or replacement of the upstream system of record. diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 1dbc2a3..5444bce 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -27,6 +27,13 @@ TrustSignal fits behind an existing workflow such as: The upstream platform remains the system of record. TrustSignal adds an integrity layer and returns technical verification artifacts that can be stored alongside the workflow record. +Start with these evaluator assets: + +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) + ## Technical Detail The public evaluation path in this repository is the `/api/v1/*` surface: diff --git a/docs/partner-eval/quickstart.md b/docs/partner-eval/quickstart.md new file mode 100644 index 0000000..d66f066 --- /dev/null +++ b/docs/partner-eval/quickstart.md @@ -0,0 +1,101 @@ +# TrustSignal Evaluator Quickstart + +## Problem + +A partner engineer evaluating TrustSignal needs a fast path to see what goes in, what comes back, and how later verification works without reading the entire repository first. + +## Integrity Model + +TrustSignal is evidence integrity infrastructure. It acts as an integrity layer for existing workflows by accepting a verification request, returning verification signals, issuing signed verification receipts, and exposing verifiable provenance metadata for later verification. + +## Integration Fit + +Start with these evaluator assets: + +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +- [Postman local environment](/Users/christopher/Projects/trustsignal/postman/TrustSignal.local.postman_environment.json) + +The 5-minute evaluator path uses the public `/api/v1/*` lifecycle already documented in this repository: + +```mermaid +sequenceDiagram + participant C as Client + participant T as TrustSignal API + + C->>T: POST /api/v1/verify + T-->>C: verification signals + signed verification receipt + C->>T: GET /api/v1/receipt/{receiptId} + T-->>C: stored receipt view + C->>T: POST /api/v1/receipt/{receiptId}/verify + T-->>C: later verification status +``` + +Use this path when you want to confirm that TrustSignal can fit behind an existing workflow and produce an audit-ready verification artifact. + +## Technical Detail + +### Step 1: Submit An Artifact Hash And Verification Request + +Request body: [verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/verify" \ + -H "Content-Type: application/json" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + --data @examples/verification-request.json +``` + +Expected response shape: [verification-response.json](/Users/christopher/Projects/trustsignal/examples/verification-response.json) + +Key public-safe outputs to look for: + +- `decision` as the current verification signal +- `receiptId` as the stable handle for later retrieval +- `receiptSignature` as the signed verification receipt artifact +- `anchor.subjectDigest` as verifiable provenance metadata when available +- `revocation.status` as current lifecycle state + +### Step 2: Retrieve The Stored Receipt + +```bash +curl "$TRUSTSIGNAL_BASE_URL/api/v1/receipt/$RECEIPT_ID" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +Expected response shape: [verification-receipt.json](/Users/christopher/Projects/trustsignal/examples/verification-receipt.json) + +This response shows the stored receipt view, the canonical receipt payload, and the PDF URL used for evaluator review. + +### Step 3: Run Later Verification + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/receipt/$RECEIPT_ID/verify" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +Expected response shape: [verification-status.json](/Users/christopher/Projects/trustsignal/examples/verification-status.json) + +This later verification step is how a workflow or reviewer confirms that the stored receipt still verifies before audit, handoff, or downstream reliance. + +### Step 4: Review Optional Lifecycle Actions + +If your evaluation includes lifecycle controls that are already public in the contract: + +- `POST /api/v1/receipt/{receiptId}/revoke` returns authorized revocation state +- `POST /api/v1/anchor/{receiptId}` returns provenance-state metadata when enabled + +These operations are part of the public lifecycle, but they are not required to validate the core evaluator path. + +## What This Evaluator Path Demonstrates + +- TrustSignal can fit behind an existing workflow without replacing the system of record +- the API returns verification signals and signed verification receipts in one flow +- stored receipts can be retrieved later +- later verification is a distinct lifecycle step +- verifiable provenance metadata is available through the public contract where supported + +## Claims Boundary + +This evaluator path demonstrates a technical verification lifecycle. It does not demonstrate legal determinations, compliance certification, fraud adjudication, or system-of-record replacement. diff --git a/postman/TrustSignal.local.postman_environment.json b/postman/TrustSignal.local.postman_environment.json new file mode 100644 index 0000000..d6be976 --- /dev/null +++ b/postman/TrustSignal.local.postman_environment.json @@ -0,0 +1,45 @@ +{ + "id": "d23e4a64-5f4d-4fd0-9387-2d6d5dc7c111", + "name": "TrustSignal Local Evaluator Environment", + "values": [ + { + "key": "base_url", + "value": "http://localhost:3001", + "type": "default", + "enabled": true + }, + { + "key": "api_key", + "value": "replace-with-api-key", + "type": "secret", + "enabled": true + }, + { + "key": "receipt_id", + "value": "replace-after-verify", + "type": "default", + "enabled": true + }, + { + "key": "issuer_id", + "value": "replace-for-revoke-tests", + "type": "default", + "enabled": true + }, + { + "key": "signature_timestamp", + "value": "2026-03-12T15:24:00.000Z", + "type": "default", + "enabled": true + }, + { + "key": "issuer_signature", + "value": "replace-for-revoke-tests", + "type": "secret", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2026-03-12T16:00:00.000Z", + "_postman_exported_using": "Codex" +} diff --git a/postman/TrustSignal.postman_collection.json b/postman/TrustSignal.postman_collection.json new file mode 100644 index 0000000..7a382b3 --- /dev/null +++ b/postman/TrustSignal.postman_collection.json @@ -0,0 +1,186 @@ +{ + "info": { + "name": "TrustSignal Evaluator Trial Path", + "description": "Public evaluator collection for the TrustSignal verification lifecycle. This collection demonstrates how to submit a verification request, receive signed verification receipts and verification signals, retrieve the stored receipt, run later verification, and review authorized lifecycle actions using the public contract only.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { + "key": "base_url", + "value": "{{base_url}}" + }, + { + "key": "api_key", + "value": "{{api_key}}" + }, + { + "key": "receipt_id", + "value": "{{receipt_id}}" + }, + { + "key": "issuer_id", + "value": "{{issuer_id}}" + }, + { + "key": "signature_timestamp", + "value": "{{signature_timestamp}}" + }, + { + "key": "issuer_signature", + "value": "{{issuer_signature}}" + } + ], + "item": [ + { + "name": "1. Create Verification", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-api-key", + "value": "{{api_key}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"bundleId\": \"verification-2026-03-12-001\",\n \"transactionType\": \"deed_transfer\",\n \"ron\": {\n \"provider\": \"source-system\",\n \"notaryId\": \"NOTARY-EXAMPLE-01\",\n \"commissionState\": \"IL\",\n \"sealPayload\": \"simulated-seal-payload\"\n },\n \"doc\": {\n \"docHash\": \"0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa\"\n },\n \"policy\": {\n \"profile\": \"CONTROL_CC_001\"\n },\n \"property\": {\n \"parcelId\": \"PARCEL-EXAMPLE-1001\",\n \"county\": \"Cook\",\n \"state\": \"IL\"\n },\n \"timestamp\": \"2026-03-12T15:24:00.000Z\"\n}" + }, + "description": "Submit a public verification request from an existing workflow. The response should include verification signals, a signed verification receipt, and public-safe verifiable provenance metadata for later verification.", + "url": { + "raw": "{{base_url}}/api/v1/verify", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "verify" + ] + } + }, + "response": [] + }, + { + "name": "2. Retrieve Receipt", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{api_key}}" + } + ], + "description": "Retrieve the stored receipt view using the receipt identifier returned by the verification request. This is the main read path for evaluators who want to inspect the signed verification receipt and receipt metadata.", + "url": { + "raw": "{{base_url}}/api/v1/receipt/{{receipt_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "receipt", + "{{receipt_id}}" + ] + } + }, + "response": [] + }, + { + "name": "3. Run Later Verification", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{api_key}}" + } + ], + "description": "Run later verification on the stored receipt. This public lifecycle step confirms current receipt integrity and status before audit, partner handoff, or downstream reliance.", + "url": { + "raw": "{{base_url}}/api/v1/receipt/{{receipt_id}}/verify", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "receipt", + "{{receipt_id}}", + "verify" + ] + } + }, + "response": [] + }, + { + "name": "4. Revoke Receipt", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{api_key}}" + }, + { + "key": "x-issuer-id", + "value": "{{issuer_id}}" + }, + { + "key": "x-signature-timestamp", + "value": "{{signature_timestamp}}" + }, + { + "key": "x-issuer-signature", + "value": "{{issuer_signature}}" + } + ], + "description": "Revoke a receipt when the caller is authorized. This public lifecycle action is included for evaluator completeness, but it requires issuer authorization headers in addition to the API key.", + "url": { + "raw": "{{base_url}}/api/v1/receipt/{{receipt_id}}/revoke", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "receipt", + "{{receipt_id}}", + "revoke" + ] + } + }, + "response": [] + }, + { + "name": "5. Review Provenance State", + "request": { + "method": "POST", + "header": [ + { + "key": "x-api-key", + "value": "{{api_key}}" + } + ], + "description": "Return verifiable provenance metadata for a receipt when enabled. This request is optional in the evaluator path and is included because the public contract documents the provenance-state route.", + "url": { + "raw": "{{base_url}}/api/v1/anchor/{{receipt_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "anchor", + "{{receipt_id}}" + ] + } + }, + "response": [] + } + ] +} diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md index fcf47cb..60b0ae6 100644 --- a/wiki/API-Overview.md +++ b/wiki/API-Overview.md @@ -25,6 +25,22 @@ The integration-facing `/api/v1/*` surface is the main public partner API in thi The legacy `/v1/*` surface is still present for the current JavaScript SDK and uses bearer JWT authentication. +### Evaluator Quickstart + +Start here to try the public lifecycle: + +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) + +Golden path: + +1. submit a verification request +2. receive verification signals plus a signed verification receipt +3. retrieve the stored receipt +4. run later verification + ## Technical Detail ### Integration-Facing Verification Lifecycle diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md index 780cf84..6387d6c 100644 --- a/wiki/Quick-Verification-Example.md +++ b/wiki/Quick-Verification-Example.md @@ -21,6 +21,13 @@ The example uses the current integration-facing lifecycle to create a verificati ## Integration Fit +Start here for the full evaluator path: + +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) + ```mermaid sequenceDiagram participant C as Client From d3c865c8bad82ca6166537e47c3dbc37a631eaeb Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 16:21:50 -0500 Subject: [PATCH 04/25] docs: connect technical docs to stakes and streamline evaluator onboarding --- README.md | 171 +++++++++++++++++++--------- docs/README.md | 16 +++ docs/partner-eval/api-playground.md | 13 ++- docs/partner-eval/overview.md | 20 +++- docs/partner-eval/quickstart.md | 15 ++- docs/security-summary.md | 10 +- wiki/API-Overview.md | 20 ++-- wiki/Home.md | 16 +++ wiki/Quick-Verification-Example.md | 16 ++- wiki/What-is-TrustSignal.md | 10 +- 10 files changed, 226 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 9d70fbf..a7a2352 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ TrustSignal is evidence integrity infrastructure for existing workflows. It acts ## Problem -Many teams can show that a file was uploaded, reviewed, or approved. Fewer can later verify that the same artifact is still the one tied to the decision they recorded. +High-stakes document and evidence workflows create an attack surface after collection, not just at intake. Once an artifact has been uploaded, reviewed, or approved, downstream teams still face risks such as tampered evidence, provenance loss, artifact substitution, and stale evidence that can no longer be verified later. -That gap creates audit friction, partner review friction, and avoidable evidence disputes. Workflow systems remain important, but they often need a durable verification artifact that can be retrieved and checked later. +Those risks matter in audit, compliance, partner-review, and trust-sensitive workflows because the evidence is often challenged after collection rather than at the moment it first entered the system. TrustSignal is designed for workflows where later auditability matters because the artifact, its provenance, or the surrounding workflow record may be questioned later. ## Integrity Model -TrustSignal addresses that gap by accepting a verification request, evaluating it against configured checks, and issuing a signed verification receipt. The receipt gives downstream systems a stable handle for later verification, receipt retrieval, lifecycle checks, and verifiable provenance. +TrustSignal addresses that problem class by accepting a verification request, evaluating it against configured checks, and issuing a signed verification receipt. The receipt gives downstream systems a stable handle for later verification, receipt retrieval, lifecycle checks, and verifiable provenance. TrustSignal provides: @@ -28,20 +28,38 @@ TrustSignal provides: - later verification capability - existing workflow integration through the public API boundary -## Integration Fit +## Evaluate The API -TrustSignal is designed to sit behind an existing workflow such as: +Start here if you are evaluating the public verification lifecycle: -- a compliance evidence pipeline -- a partner portal -- an intake or case-management system -- a deed or property-record workflow +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +- [Postman local environment](/Users/christopher/Projects/trustsignal/postman/TrustSignal.local.postman_environment.json) -The upstream platform remains the system of record. TrustSignal adds an integrity layer at the boundary and returns technical verification artifacts that the upstream workflow can store and use later. +Golden path: -## Technical Detail +1. submit a verification request +2. receive verification signals plus a signed verification receipt +3. retrieve the stored receipt +4. run later verification + +## What You Will See + +The evaluator path is designed to show the core value before full production integration work: + +- artifact intake through the public API +- signed verification receipt issuance +- verification signals that can be stored in an existing workflow +- later verification against the stored receipt state +- visible handling for tampered evidence or stale evidence through the later verification lifecycle + +## Quick Evaluator Path -The current partner-facing lifecycle in this repository is the `/api/v1/*` surface: +The fastest path in this repository is the public `/api/v1/*` evaluator flow. It is a deliberate evaluator path, not a shortcut around production controls. + +The current partner-facing lifecycle in this repository is: - `POST /api/v1/verify` - `GET /api/v1/receipt/:receiptId` @@ -51,6 +69,81 @@ The current partner-facing lifecycle in this repository is the `/api/v1/*` surfa - `POST /api/v1/anchor/:receiptId` - `GET /api/v1/receipts` +## Minimal Local Setup + +Prerequisites: + +- Node.js `>= 18` +- npm `>= 9` +- PostgreSQL `>= 14` for `apps/api` + +Minimal setup: + +```bash +npm install +cp .env.example .env.local +cp apps/api/.env.example apps/api/.env +npm -w apps/api run db:generate +npm -w apps/api run db:push +npm -w apps/api run dev +``` + +In a second terminal: + +```bash +npm -w apps/web run dev +``` + +Default local ports: + +- web app: `http://localhost:3000` +- API: `http://localhost:3001` + +## Run The Demo + +Once the local API is running, use the evaluator quickstart or the public examples directly: + +```bash +curl -X POST "http://localhost:3001/api/v1/verify" \ + -H "Content-Type: application/json" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + --data @examples/verification-request.json +``` + +Then retrieve the stored receipt and run later verification: + +```bash +curl "http://localhost:3001/api/v1/receipt/$RECEIPT_ID" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" + +curl -X POST "http://localhost:3001/api/v1/receipt/$RECEIPT_ID/verify" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +## What The Demo Proves + +The evaluator flow demonstrates that: + +- TrustSignal can fit behind an existing workflow without replacing the system of record +- the API returns signed verification receipts and verification signals in one flow +- later verification is explicit and separate from initial receipt issuance +- the system is built for attack surfaces such as tampered evidence, provenance loss, and artifact substitution in later review paths + +## Integration Fit + +TrustSignal is designed to sit behind an existing workflow such as: + +- a compliance evidence pipeline +- a partner portal +- an intake or case-management system +- a deed or property-record workflow + +The upstream platform remains the system of record. TrustSignal adds an integrity layer at the boundary and returns technical verification artifacts that the upstream workflow can store and use later. + +## Full Local Development Setup + +The local evaluator path is intentionally constrained. Local development defaults are a deliberate evaluator and development path, and they fail closed where production trust assumptions are not satisfied. + Authentication is `x-api-key` with scoped access. Revocation additionally requires issuer authorization headers: `x-issuer-id`, `x-signature-timestamp`, and `x-issuer-signature`. The repository also still includes a legacy JWT-authenticated `/v1/*` surface used by the current JavaScript SDK: @@ -59,26 +152,23 @@ The repository also still includes a legacy JWT-authenticated `/v1/*` surface us - `GET /v1/status/:bundleId` - `POST /v1/revoke` -## Evaluate The API +## Production Deployment Requirements -Start here if you are evaluating the public verification lifecycle: +Production deployment requires explicit authentication, signing configuration, and environment setup. Public documentation should be read as architecturally mature and bounded, not as a claim that every deployment control is satisfied automatically. -- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) -- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) -- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) -- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) -- [Postman local environment](/Users/christopher/Projects/trustsignal/postman/TrustSignal.local.postman_environment.json) +For production use, plan for at least: -Golden path: +- explicit API-key and JWT configuration +- signing configuration and key management through environment setup +- receipt lifecycle checks before downstream reliance +- database and network security controls appropriate for the deployment environment +- environment-specific operational controls outside this repository -1. submit a verification request -2. receive verification signals plus a signed verification receipt -3. retrieve the stored receipt -4. run later verification +Fail-closed defaults are part of the security posture. They are meant to prevent the system from silently assuming production trust conditions that have not been configured. ## Public API Contract And Examples -The public evaluation artifacts added in this repo are: +The public evaluation artifacts in this repo are: - [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) - [verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) @@ -98,6 +188,7 @@ Public-facing security properties for this repository are: - signed verification receipts returned with verification responses - later verification of stored receipt integrity and status - explicit lifecycle boundaries for read, revoke, and provenance-state operations +- fail-closed defaults where production trust assumptions are not satisfied See [docs/security-summary.md](/Users/christopher/Projects/trustsignal/docs/security-summary.md), [SECURITY_CHECKLIST.md](/Users/christopher/Projects/trustsignal/SECURITY_CHECKLIST.md), and [docs/SECURITY.md](/Users/christopher/Projects/trustsignal/docs/SECURITY.md) for the current public-safe security summary and repository guardrails. @@ -115,36 +206,6 @@ TrustSignal does not provide: DeedShield is the current application surface in this repository. The broader product framing remains TrustSignal as evidence integrity infrastructure and an integrity layer for existing workflows. -## Local Development - -Prerequisites: - -- Node.js `>= 18` -- npm `>= 9` -- PostgreSQL `>= 14` for `apps/api` - -Quickstart: - -```bash -npm install -cp .env.example .env.local -cp apps/api/.env.example apps/api/.env -npm -w apps/api run db:generate -npm -w apps/api run db:push -npm -w apps/api run dev -``` - -In a second terminal: - -```bash -npm -w apps/web run dev -``` - -Default local ports: - -- web app: `http://localhost:3000` -- API: `http://localhost:3001` - ## Validation Relevant repository checks include: diff --git a/docs/README.md b/docs/README.md index dbc790b..7e52fc7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,14 @@ This folder is organized into active, canonical documents and archived historical material. +## Problem + +TrustSignal documentation is written for evaluators and implementers working in workflows where later auditability matters. The main attack surface is not only bad data at intake, but also tampered evidence, provenance loss, artifact substitution, and stale evidence that cannot be verified later. + +## Integrity Model + +TrustSignal is evidence integrity infrastructure. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflow integration. + ## Evaluator Quickstart Start here if you want to evaluate the public verification lifecycle quickly: @@ -20,6 +28,14 @@ Golden path: 3. retrieve the stored receipt 4. run later verification +## Integration Fit + +The evaluator and demo paths are deliberate evaluator paths. They show the verification lifecycle safely before production integration and do not remove production security requirements. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ## Canonical Documentation - `final/01_EXECUTIVE_SUMMARY.md` - `final/02_ARCHITECTURE_AND_BOUNDARIES.md` diff --git a/docs/partner-eval/api-playground.md b/docs/partner-eval/api-playground.md index cc561ef..e0d33d8 100644 --- a/docs/partner-eval/api-playground.md +++ b/docs/partner-eval/api-playground.md @@ -2,13 +2,13 @@ ## Problem -Evaluators often want a single page that shows the public API lifecycle, the exact artifacts to use, and the smallest realistic set of commands for testing. +Evaluators often want a single page that shows the public API lifecycle, the exact artifacts to use, and the smallest realistic set of commands for testing. The key attack surface is later, not just at intake: tampered evidence, provenance loss, artifact substitution, and stale records in later review paths. ## Integrity Model TrustSignal provides verification signals, signed verification receipts, verifiable provenance metadata, and later verification through the public `/api/v1/*` surface. -## Integration Fit +## Evaluator Path Use this playground when you want to test the golden path quickly: @@ -27,6 +27,14 @@ Canonical assets: - [verification-status.json](/Users/christopher/Projects/trustsignal/examples/verification-status.json) - [TrustSignal.postman_collection.json](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +## Integration Fit + +The playground is a deliberate evaluator path. It is designed to show the verification lifecycle safely before production integration requirements are fully configured. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ## Technical Detail ### Environment @@ -114,6 +122,7 @@ Focus the evaluation on: - whether signed verification receipts are easy to retrieve later - whether later verification is explicit and easy to re-run - whether the public contract exposes verifiable provenance without exposing internal implementation details +- whether the lifecycle is credible for workflows vulnerable to tampered evidence and provenance loss after collection ## Claims Boundary diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 5444bce..569b597 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -2,7 +2,7 @@ ## Problem -Teams often have a workflow record that says an artifact was reviewed, approved, or submitted, but they cannot easily prove later that the same artifact is still the one tied to that decision. +Teams often have a workflow record that says an artifact was reviewed, approved, or submitted, but they cannot easily prove later that the same artifact is still the one tied to that decision. In high-loss and highly scrutinized workflows, that creates an attack surface around tampered evidence, provenance loss, artifact substitution, and stale evidence in later review paths. ## Integrity Model @@ -16,6 +16,17 @@ TrustSignal is designed to support: - audit-ready evidence - later verification without replacing the upstream workflow owner +## Evaluator Path + +Start with these evaluator assets: + +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) + +The evaluator flow is designed to show the verification lifecycle safely before production integration requirements are introduced. + ## Integration Fit TrustSignal fits behind an existing workflow such as: @@ -27,12 +38,9 @@ TrustSignal fits behind an existing workflow such as: The upstream platform remains the system of record. TrustSignal adds an integrity layer and returns technical verification artifacts that can be stored alongside the workflow record. -Start with these evaluator assets: +## Production Deployment Requirements -- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) -- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) -- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) -- [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +Local and evaluator paths are deliberate evaluator paths. Production deployment requires explicit authentication, signing configuration, and environment setup. Fail-closed defaults are part of the security posture and are intended to stop unsafe production assumptions from being applied implicitly. ## Technical Detail diff --git a/docs/partner-eval/quickstart.md b/docs/partner-eval/quickstart.md index d66f066..db0b7df 100644 --- a/docs/partner-eval/quickstart.md +++ b/docs/partner-eval/quickstart.md @@ -2,13 +2,13 @@ ## Problem -A partner engineer evaluating TrustSignal needs a fast path to see what goes in, what comes back, and how later verification works without reading the entire repository first. +A partner engineer evaluating TrustSignal needs a fast path to see what goes in, what comes back, and how later verification works. The relevant attack surface is not only bad input at intake, but also tampered evidence, provenance loss, artifact substitution, and stale evidence that becomes hard to defend later. ## Integrity Model TrustSignal is evidence integrity infrastructure. It acts as an integrity layer for existing workflows by accepting a verification request, returning verification signals, issuing signed verification receipts, and exposing verifiable provenance metadata for later verification. -## Integration Fit +## Evaluator Path Start with these evaluator assets: @@ -32,7 +32,15 @@ sequenceDiagram T-->>C: later verification status ``` -Use this path when you want to confirm that TrustSignal can fit behind an existing workflow and produce an audit-ready verification artifact. +Use this path when you want to confirm that TrustSignal can fit behind an existing workflow and produce an audit-ready verification artifact before production integration work begins. + +## Integration Fit + +The evaluator path is a deliberate evaluator path. It shows the verification lifecycle safely before production authentication, signing, and environment requirements are fully configured. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. ## Technical Detail @@ -95,6 +103,7 @@ These operations are part of the public lifecycle, but they are not required to - stored receipts can be retrieved later - later verification is a distinct lifecycle step - verifiable provenance metadata is available through the public contract where supported +- the system is built for workflows where tampered evidence and provenance loss matter after collection ## Claims Boundary diff --git a/docs/security-summary.md b/docs/security-summary.md index 4601b76..91a2d73 100644 --- a/docs/security-summary.md +++ b/docs/security-summary.md @@ -2,11 +2,11 @@ ## Problem -Partners and evaluators need a public-safe summary of the TrustSignal security boundary without exposing internal implementation details that are not required for integration. +Partners and evaluators need a public-safe security summary that explains the attack surface without exposing internal implementation details. In high-stakes workflows, evidence can be challenged after collection through tampered evidence, provenance loss, artifact substitution, or stale records that are no longer independently verifiable. ## Integrity Model -TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflows. +TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflow integration. ## Integration Fit @@ -18,6 +18,12 @@ For the public `/api/v1/*` surface in this repository: - receipt revocation requires additional issuer authorization headers - later verification is available through a dedicated receipt verification route +Evaluator and demo flows are deliberate evaluator paths. They are designed to show the verification lifecycle safely before production integration. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ## Technical Detail TrustSignal public materials should be understood within this boundary: diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md index 60b0ae6..34177ca 100644 --- a/wiki/API-Overview.md +++ b/wiki/API-Overview.md @@ -13,19 +13,13 @@ ## Problem -Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. +Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. The relevant attack surface includes tampered evidence, provenance loss, artifact substitution, and stale evidence in later review paths. ## Integrity Model TrustSignal exposes a public verification lifecycle centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. -## Integration Fit - -The integration-facing `/api/v1/*` surface is the main public partner API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. - -The legacy `/v1/*` surface is still present for the current JavaScript SDK and uses bearer JWT authentication. - -### Evaluator Quickstart +## Evaluator Path Start here to try the public lifecycle: @@ -41,6 +35,16 @@ Golden path: 3. retrieve the stored receipt 4. run later verification +## Integration Fit + +The integration-facing `/api/v1/*` surface is the main public partner API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. + +The legacy `/v1/*` surface is still present for the current JavaScript SDK and uses bearer JWT authentication. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ## Technical Detail ### Integration-Facing Verification Lifecycle diff --git a/wiki/Home.md b/wiki/Home.md index e30dde5..d11626d 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -13,6 +13,10 @@ TrustSignal is evidence integrity infrastructure for existing workflows. It acts as an integrity layer that provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability. +## Problem + +TrustSignal is built for workflows where evidence can be challenged after collection. The relevant attack surface includes tampered evidence, provenance loss, artifact substitution, and stale records that are difficult to verify later. + ## Start Here - [What is TrustSignal](What-is-TrustSignal) @@ -21,6 +25,18 @@ TrustSignal is evidence integrity infrastructure for existing workflows. It acts - [Claims Boundary](Claims-Boundary) - [Quick Verification Example](Quick-Verification-Example) +## Evaluator Path + +Use the evaluator docs when you want to see the verification lifecycle before production integration detail: + +- [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) +- [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ## Current Boundary TrustSignal provides technical verification artifacts. It does not provide legal determinations, compliance certification, fraud adjudication, or a replacement for the upstream system of record. diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md index 6387d6c..a2eb77a 100644 --- a/wiki/Quick-Verification-Example.md +++ b/wiki/Quick-Verification-Example.md @@ -13,13 +13,13 @@ ## Problem -This example is for partner engineers who want the smallest realistic TrustSignal flow that shows what goes in, what comes back, and how later verification works. +This example is for partner engineers who want the smallest realistic TrustSignal flow that shows what goes in, what comes back, and how later verification works. It is intended for workflows where tampered evidence, provenance loss, artifact substitution, and stale evidence matter after collection. ## Integrity Model The example uses the current integration-facing lifecycle to create a verification, return verification signals plus a signed verification receipt, and later verify stored receipt state. -## Integration Fit +## Evaluator Path Start here for the full evaluator path: @@ -28,6 +28,16 @@ Start here for the full evaluator path: - [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) - [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +## Integration Fit + +This example is a deliberate evaluator path. It is designed to show the verification lifecycle before production authentication, signing, and environment requirements are fully configured. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + +## Technical Detail + ```mermaid sequenceDiagram participant C as Client @@ -44,8 +54,6 @@ sequenceDiagram A-->>C: later verification response ``` -## Technical Detail - ### Product Terms And Current API Fields | Product Term | Current API Field | diff --git a/wiki/What-is-TrustSignal.md b/wiki/What-is-TrustSignal.md index 1a670e3..b9c1add 100644 --- a/wiki/What-is-TrustSignal.md +++ b/wiki/What-is-TrustSignal.md @@ -13,12 +13,16 @@ ## Problem -Many workflow systems can show that an artifact was collected or reviewed. Fewer can later verify that the same artifact is still the one tied to the recorded decision. +Many workflow systems can show that an artifact was collected or reviewed. Fewer can later verify that the same artifact is still the one tied to the recorded decision. In high-stakes workflows, that creates an attack surface around tampered evidence, provenance loss, artifact substitution, and stale evidence in later audit or review paths. ## Integrity Model TrustSignal is evidence integrity infrastructure. It provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification for existing workflows. +## Evaluator Path + +The evaluator and demo path in this repository is a deliberate evaluator path. It is designed to show the verification lifecycle safely before production integration requirements are fully configured. + ## Integration Fit TrustSignal fits behind an existing platform such as: @@ -30,6 +34,10 @@ TrustSignal fits behind an existing platform such as: The upstream platform remains the system of record. TrustSignal adds an integrity layer at the workflow boundary. +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ## Technical Detail At a high level, the public verification lifecycle is: From cac6773c95cac60dd506d86ba8acefff168844d5 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 16:43:46 -0500 Subject: [PATCH 05/25] Add evaluator demo and lifecycle docs --- README.md | 51 ++-- demo/README.md | 42 ++++ demo/demo-script.ts | 226 ++++++++++++++++++ demo/sample-artifact.json | 20 ++ demo/tampered-artifact.json | 20 ++ docs/README.md | 12 +- docs/partner-eval/overview.md | 33 +-- package.json | 2 +- ...TrustSignal.local.postman_environment.json | 6 + postman/TrustSignal.postman_collection.json | 19 ++ wiki/API-Overview.md | 32 ++- wiki/Home.md | 24 +- wiki/What-is-TrustSignal.md | 14 +- 13 files changed, 451 insertions(+), 50 deletions(-) create mode 100644 demo/README.md create mode 100644 demo/demo-script.ts create mode 100644 demo/sample-artifact.json create mode 100644 demo/tampered-artifact.json diff --git a/README.md b/README.md index a7a2352..541af69 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,26 @@ TrustSignal provides: - later verification capability - existing workflow integration through the public API boundary -## Evaluate The API +## Demo + +The fastest evaluator path is the local 5-minute developer trial: + +```bash +npm install +npm run demo +``` + +It shows the full lifecycle in one run: + +1. artifact intake +2. verification result +3. signed receipt issuance +4. later verification +5. tampered artifact mismatch detection + +See [demo/README.md](/Users/christopher/Projects/trustsignal/demo/README.md). + +## Integration Start here if you are evaluating the public verification lifecycle: @@ -45,17 +64,7 @@ Golden path: 3. retrieve the stored receipt 4. run later verification -## What You Will See - -The evaluator path is designed to show the core value before full production integration work: - -- artifact intake through the public API -- signed verification receipt issuance -- verification signals that can be stored in an existing workflow -- later verification against the stored receipt state -- visible handling for tampered evidence or stale evidence through the later verification lifecycle - -## Quick Evaluator Path +## Technical Details The fastest path in this repository is the public `/api/v1/*` evaluator flow. It is a deliberate evaluator path, not a shortcut around production controls. @@ -69,7 +78,17 @@ The current partner-facing lifecycle in this repository is: - `POST /api/v1/anchor/:receiptId` - `GET /api/v1/receipts` -## Minimal Local Setup +## What You Will See + +The evaluator path is designed to show the core value before full production integration work: + +- artifact intake through the public API +- signed verification receipt issuance +- verification signals that can be stored in an existing workflow +- later verification against the stored receipt state +- visible handling for tampered evidence or stale evidence through the later verification lifecycle + +## Local API Development Setup Prerequisites: @@ -99,7 +118,7 @@ Default local ports: - web app: `http://localhost:3000` - API: `http://localhost:3001` -## Run The Demo +## Run The API Evaluation Flow Once the local API is running, use the evaluator quickstart or the public examples directly: @@ -120,7 +139,7 @@ curl -X POST "http://localhost:3001/api/v1/receipt/$RECEIPT_ID/verify" \ -H "x-api-key: $TRUSTSIGNAL_API_KEY" ``` -## What The Demo Proves +## What The Developer Trial Proves The evaluator flow demonstrates that: @@ -140,7 +159,7 @@ TrustSignal is designed to sit behind an existing workflow such as: The upstream platform remains the system of record. TrustSignal adds an integrity layer at the boundary and returns technical verification artifacts that the upstream workflow can store and use later. -## Full Local Development Setup +## Integration Boundary Notes The local evaluator path is intentionally constrained. Local development defaults are a deliberate evaluator and development path, and they fail closed where production trust assumptions are not satisfied. diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..fc830fd --- /dev/null +++ b/demo/README.md @@ -0,0 +1,42 @@ +# TrustSignal 5-Minute Developer Trial + +This demo shows the TrustSignal verification lifecycle in one local command: + +artifact -> verification -> signed receipt -> later verification -> tampered artifact detection + +It is intentionally local-only and requires no external services or environment variables. + +## Run + +```bash +git clone +cd trustsignal +npm install +npm run demo +``` + +## What It Does + +1. Loads [`sample-artifact.json`](./sample-artifact.json) +2. Computes and prints the artifact hash +3. Generates a verification result +4. Issues a signed verification receipt +5. Persists the receipt to `demo/output/verification-receipt.json` +6. Reloads that receipt for later verification +7. Verifies that a tampered artifact no longer matches the stored receipt + +## Expected Output + +The command prints: + +- artifact hash +- verification result +- receipt issuance +- later verification check +- tampered artifact mismatch + +## Files + +- [`sample-artifact.json`](./sample-artifact.json): canonical artifact used for issuance +- [`tampered-artifact.json`](./tampered-artifact.json): altered artifact used to demonstrate mismatch detection +- [`demo-script.ts`](./demo-script.ts): local verification lifecycle demo diff --git a/demo/demo-script.ts b/demo/demo-script.ts new file mode 100644 index 0000000..ad8fed3 --- /dev/null +++ b/demo/demo-script.ts @@ -0,0 +1,226 @@ +import { mkdir, readFile, writeFile } from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; + +import { exportJWK, generateKeyPair } from "jose"; + +import { canonicalizeJson } from "../packages/core/src/canonicalize.ts"; +import { keccak256Utf8 } from "../packages/core/src/hashing.ts"; +import { buildReceipt, computeReceiptHash, toUnsignedReceiptPayload } from "../packages/core/src/receipt.ts"; +import { signReceiptPayload, verifyReceiptSignature } from "../packages/core/src/receiptSigner.ts"; +import type { BundleInput, Receipt, VerificationResult } from "../packages/core/src/types.ts"; + +type DemoArtifact = { + artifactId: string; + artifactType: string; + sourceSystem: string; + collectedAt: string; + subject: { + parcelId: string; + county: string; + state: string; + }; + document: { + title: string; + documentNumber: string; + digestSource: string; + }; + parties: { + grantor: string; + grantee: string; + }; +}; + +type PersistedReceipt = { + verificationId: string; + artifactHash: string; + receipt: Receipt; + verificationResult: VerificationResult; + issuer: { + kid: string; + publicJwk: Awaited>; + }; +}; + +const currentDir = path.dirname(fileURLToPath(import.meta.url)); +const outputDir = path.join(currentDir, "output"); +const persistedReceiptPath = path.join(outputDir, "verification-receipt.json"); + +function formatStep(title: string) { + console.log(`\n=== ${title} ===`); +} + +async function loadArtifact(fileName: string): Promise { + const filePath = path.join(currentDir, fileName); + const contents = await readFile(filePath, "utf8"); + return JSON.parse(contents) as DemoArtifact; +} + +function hashArtifact(artifact: DemoArtifact): string { + return keccak256Utf8(canonicalizeJson(artifact)); +} + +function toBundleInput(artifact: DemoArtifact, artifactHash: string): BundleInput { + return { + bundleId: artifact.artifactId, + transactionType: artifact.artifactType, + ron: { + provider: artifact.sourceSystem, + notaryId: "NOTARY-DEMO-01", + commissionState: artifact.subject.state, + sealPayload: "demo-seal-payload" + }, + doc: { + docHash: artifactHash + }, + property: artifact.subject, + policy: { + profile: "DEMO_INTEGRITY_V1" + }, + timestamp: artifact.collectedAt + }; +} + +function runVerification(artifact: DemoArtifact, artifactHash: string): VerificationResult { + return { + decision: "ALLOW", + reasons: [ + "artifact accepted into verification lifecycle", + "signed verification receipt issued" + ], + riskScore: 11, + checks: [ + { + checkId: "artifact.hash.bound", + status: "PASS", + details: `artifact hash recorded: ${artifactHash}` + }, + { + checkId: "artifact.provenance.source", + status: "PASS", + details: `source recorded: ${artifact.sourceSystem}` + }, + { + checkId: "artifact.provenance.subject", + status: "PASS", + details: `${artifact.subject.county} County ${artifact.subject.state} parcel ${artifact.subject.parcelId}` + } + ] + }; +} + +async function issueReceipt(artifact: DemoArtifact): Promise { + const artifactHash = hashArtifact(artifact); + const bundleInput = toBundleInput(artifact, artifactHash); + const verificationResult = runVerification(artifact, artifactHash); + const receipt = buildReceipt(bundleInput, verificationResult, "trustsignal-demo"); + + const { privateKey, publicKey } = await generateKeyPair("EdDSA"); + const privateJwk = await exportJWK(privateKey); + const publicJwk = await exportJWK(publicKey); + + const unsignedReceipt = toUnsignedReceiptPayload(receipt); + const receiptSignature = await signReceiptPayload(unsignedReceipt, { + privateJwk, + kid: "trustsignal-demo-key" + }); + + const signedReceipt: Receipt = { + ...receipt, + receiptSignature + }; + + return { + verificationId: signedReceipt.receiptId, + artifactHash, + receipt: signedReceipt, + verificationResult, + issuer: { + kid: "trustsignal-demo-key", + publicJwk + } + }; +} + +async function persistReceipt(record: PersistedReceipt): Promise { + await mkdir(outputDir, { recursive: true }); + await writeFile(persistedReceiptPath, `${JSON.stringify(record, null, 2)}\n`, "utf8"); +} + +async function loadPersistedReceipt(): Promise { + const contents = await readFile(persistedReceiptPath, "utf8"); + return JSON.parse(contents) as PersistedReceipt; +} + +async function verifyLater(artifact: DemoArtifact, persisted: PersistedReceipt) { + const artifactHash = hashArtifact(artifact); + const unsignedReceipt = toUnsignedReceiptPayload(persisted.receipt); + const recomputedReceiptHash = computeReceiptHash(unsignedReceipt); + const signatureResult = + persisted.receipt.receiptSignature == null + ? { + verified: false, + keyResolved: false, + payloadMatches: false, + kid: persisted.issuer.kid, + alg: "EdDSA", + reason: "missing_signature" + } + : await verifyReceiptSignature(unsignedReceipt, persisted.receipt.receiptSignature, { + [persisted.issuer.kid]: persisted.issuer.publicJwk + }); + + return { + artifactHash, + matchesStoredArtifact: artifactHash === persisted.artifactHash, + receiptHashMatches: recomputedReceiptHash === persisted.receipt.receiptHash, + signatureVerified: signatureResult.verified, + verificationResult: persisted.verificationResult.decision, + receiptId: persisted.receipt.receiptId + }; +} + +async function main() { + const artifact = await loadArtifact("sample-artifact.json"); + const tamperedArtifact = await loadArtifact("tampered-artifact.json"); + + formatStep("Artifact Intake"); + console.log(`artifact id: ${artifact.artifactId}`); + console.log(`artifact hash: ${hashArtifact(artifact)}`); + + formatStep("Verification Result + Signed Receipt"); + const issuedReceipt = await issueReceipt(artifact); + console.log(`verification result: ${issuedReceipt.verificationResult.decision}`); + console.log(`receipt issuance: persisted signed receipt for ${issuedReceipt.receipt.receiptId}`); + console.log(`receipt path: ${persistedReceiptPath}`); + + await persistReceipt(issuedReceipt); + + formatStep("Later Verification"); + const persistedReceipt = await loadPersistedReceipt(); + const laterVerification = await verifyLater(artifact, persistedReceipt); + console.log(`later verification check: ${laterVerification.matchesStoredArtifact ? "MATCH" : "MISMATCH"}`); + console.log(`receipt hash verified: ${laterVerification.receiptHashMatches ? "YES" : "NO"}`); + console.log(`signature verified: ${laterVerification.signatureVerified ? "YES" : "NO"}`); + + formatStep("Tampered Artifact Detection"); + console.log(`tampered artifact hash: ${hashArtifact(tamperedArtifact)}`); + const tamperedVerification = await verifyLater(tamperedArtifact, persistedReceipt); + console.log( + `tampered artifact mismatch: ${tamperedVerification.matchesStoredArtifact ? "NOT DETECTED" : "DETECTED"}` + ); + + if ( + !laterVerification.matchesStoredArtifact || + !laterVerification.receiptHashMatches || + !laterVerification.signatureVerified || + tamperedVerification.matchesStoredArtifact + ) { + throw new Error("Demo verification lifecycle failed"); + } +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exitCode = 1; +}); diff --git a/demo/sample-artifact.json b/demo/sample-artifact.json new file mode 100644 index 0000000..283783a --- /dev/null +++ b/demo/sample-artifact.json @@ -0,0 +1,20 @@ +{ + "artifactId": "artifact-2026-03-12-001", + "artifactType": "deed-transfer-evidence", + "sourceSystem": "county-intake-demo", + "collectedAt": "2026-03-12T15:24:00.000Z", + "subject": { + "parcelId": "PARCEL-EXAMPLE-1001", + "county": "Cook", + "state": "IL" + }, + "document": { + "title": "Warranty Deed Packet", + "documentNumber": "2026-0001842", + "digestSource": "Simulated deed packet captured during evaluator demo" + }, + "parties": { + "grantor": "Example Seller LLC", + "grantee": "Example Buyer Trust" + } +} diff --git a/demo/tampered-artifact.json b/demo/tampered-artifact.json new file mode 100644 index 0000000..e19500b --- /dev/null +++ b/demo/tampered-artifact.json @@ -0,0 +1,20 @@ +{ + "artifactId": "artifact-2026-03-12-001", + "artifactType": "deed-transfer-evidence", + "sourceSystem": "county-intake-demo", + "collectedAt": "2026-03-12T15:24:00.000Z", + "subject": { + "parcelId": "PARCEL-EXAMPLE-1001", + "county": "Cook", + "state": "IL" + }, + "document": { + "title": "Warranty Deed Packet", + "documentNumber": "2026-0001842", + "digestSource": "Simulated deed packet captured after unauthorized modification" + }, + "parties": { + "grantor": "Example Seller LLC", + "grantee": "Substituted Buyer Trust" + } +} diff --git a/docs/README.md b/docs/README.md index 7e52fc7..4b2b23e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,15 @@ TrustSignal documentation is written for evaluators and implementers working in TrustSignal is evidence integrity infrastructure. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflow integration. -## Evaluator Quickstart +## Demo + +Start with the local developer trial if you want the fastest technical evaluation: + +- [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) + +The demo shows artifact hashing, verification, signed receipt issuance, later verification, and tampered artifact mismatch detection without external services. + +## Integration Start here if you want to evaluate the public verification lifecycle quickly: @@ -28,6 +36,8 @@ Golden path: 3. retrieve the stored receipt 4. run later verification +## Technical Details + ## Integration Fit The evaluator and demo paths are deliberate evaluator paths. They show the verification lifecycle safely before production integration and do not remove production security requirements. diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 569b597..5fa7041 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -13,10 +13,15 @@ TrustSignal is designed to support: - signed verification receipts - verification signals - verifiable provenance -- audit-ready evidence - later verification without replacing the upstream workflow owner -## Evaluator Path +## Demo + +Start with the local developer trial when you want the shortest path to the verification lifecycle: + +- [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) + +## Integration Start with these evaluator assets: @@ -27,6 +32,18 @@ Start with these evaluator assets: The evaluator flow is designed to show the verification lifecycle safely before production integration requirements are introduced. +## Technical Details + +The public evaluation path in this repository is the `/api/v1/*` surface: + +1. Submit a verification request to `POST /api/v1/verify`. +2. Receive a decision, signed verification receipt, and provenance metadata. +3. Retrieve the stored receipt at `GET /api/v1/receipt/{receiptId}`. +4. Run later verification at `POST /api/v1/receipt/{receiptId}/verify`. +5. Use authorized lifecycle actions such as revocation and provenance-state retrieval where needed. + +Canonical contract and payload examples live in [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) and the [`examples/`](../../examples) directory. + ## Integration Fit TrustSignal fits behind an existing workflow such as: @@ -41,15 +58,3 @@ The upstream platform remains the system of record. TrustSignal adds an integrit ## Production Deployment Requirements Local and evaluator paths are deliberate evaluator paths. Production deployment requires explicit authentication, signing configuration, and environment setup. Fail-closed defaults are part of the security posture and are intended to stop unsafe production assumptions from being applied implicitly. - -## Technical Detail - -The public evaluation path in this repository is the `/api/v1/*` surface: - -1. Submit a verification request to `POST /api/v1/verify`. -2. Receive a decision, signed verification receipt, and provenance metadata. -3. Retrieve the stored receipt at `GET /api/v1/receipt/{receiptId}`. -4. Run later verification at `POST /api/v1/receipt/{receiptId}/verify`. -5. Use authorized lifecycle actions such as revocation and provenance-state retrieval where needed. - -Canonical contract and payload examples live in [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) and the [`examples/`](../../examples) directory. diff --git a/package.json b/package.json index 02cc7f2..0440033 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "validate": "npm run lint && npm run typecheck && npm test && npm run build", "smoke:signed-receipt": "bash scripts/smoke-signed-receipt.sh", "security:audit": "npm audit --omit=dev --audit-level=high", - "demo": "tsx scripts/demo.ts", + "demo": "tsx demo/demo-script.ts", "demo:vanta-terminal": "bash scripts/demo-vanta-terminal.sh", "demo:playwright": "node scripts/playwright-vanta-command-center.mjs", "start:verify": "node src/api/verify.js", diff --git a/postman/TrustSignal.local.postman_environment.json b/postman/TrustSignal.local.postman_environment.json index d6be976..d2213ef 100644 --- a/postman/TrustSignal.local.postman_environment.json +++ b/postman/TrustSignal.local.postman_environment.json @@ -14,6 +14,12 @@ "type": "secret", "enabled": true }, + { + "key": "verification_id", + "value": "replace-after-verify", + "type": "default", + "enabled": true + }, { "key": "receipt_id", "value": "replace-after-verify", diff --git a/postman/TrustSignal.postman_collection.json b/postman/TrustSignal.postman_collection.json index 7a382b3..b2100c3 100644 --- a/postman/TrustSignal.postman_collection.json +++ b/postman/TrustSignal.postman_collection.json @@ -13,6 +13,10 @@ "key": "api_key", "value": "{{api_key}}" }, + { + "key": "verification_id", + "value": "{{verification_id}}" + }, { "key": "receipt_id", "value": "{{receipt_id}}" @@ -33,6 +37,21 @@ "item": [ { "name": "1. Create Verification", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const response = pm.response.json();", + "if (response.receiptId) {", + " pm.environment.set('verification_id', response.receiptId);", + " pm.environment.set('receipt_id', response.receiptId);", + "}" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md index 34177ca..aace22a 100644 --- a/wiki/API-Overview.md +++ b/wiki/API-Overview.md @@ -13,13 +13,19 @@ ## Problem -Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. The relevant attack surface includes tampered evidence, provenance loss, artifact substitution, and stale evidence in later review paths. +Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. The relevant attack surface includes evidence tampering after collection, artifact substitution attacks, provenance loss in compliance workflows, stale evidence during audit review, and documentation chains that cannot be verified later. ## Integrity Model TrustSignal exposes a public verification lifecycle centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. -## Evaluator Path +## Demo + +Start with the local developer trial for the fastest lifecycle walkthrough: + +- [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) + +## Integration Start here to try the public lifecycle: @@ -35,17 +41,7 @@ Golden path: 3. retrieve the stored receipt 4. run later verification -## Integration Fit - -The integration-facing `/api/v1/*` surface is the main public partner API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. - -The legacy `/v1/*` surface is still present for the current JavaScript SDK and uses bearer JWT authentication. - -## Production Deployment Requirements - -Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. - -## Technical Detail +## Technical Details ### Integration-Facing Verification Lifecycle @@ -65,6 +61,16 @@ Local development defaults are intentionally constrained and fail closed where p - `x-signature-timestamp` - `x-issuer-signature` +## Integration Fit + +The integration-facing `/api/v1/*` surface is the main public partner API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. + +The legacy `/v1/*` surface is still present for the current JavaScript SDK and uses bearer JWT authentication. + +## Production Deployment Requirements + +Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. + ### Additional Integration Routes | Method | Path | Purpose | diff --git a/wiki/Home.md b/wiki/Home.md index d11626d..3899a4d 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -15,7 +15,13 @@ TrustSignal is evidence integrity infrastructure for existing workflows. It acts ## Problem -TrustSignal is built for workflows where evidence can be challenged after collection. The relevant attack surface includes tampered evidence, provenance loss, artifact substitution, and stale records that are difficult to verify later. +TrustSignal is built for workflows where evidence can be challenged after collection. The relevant attack surface includes evidence tampering after collection, artifact substitution attacks, provenance loss across compliance workflows, stale evidence during audit review, and documentation chains that cannot be verified later. + +High-loss environments create incentives for these attack paths because downstream reviewers often must rely on artifacts long after the original collection event. + +## Integrity Model + +TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability as an integrity layer for an existing system of record. ## Start Here @@ -25,7 +31,11 @@ TrustSignal is built for workflows where evidence can be challenged after collec - [Claims Boundary](Claims-Boundary) - [Quick Verification Example](Quick-Verification-Example) -## Evaluator Path +## Demo + +- [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) + +## Integration Use the evaluator docs when you want to see the verification lifecycle before production integration detail: @@ -33,6 +43,16 @@ Use the evaluator docs when you want to see the verification lifecycle before pr - [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) - [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) +## Technical Details + +The public verification lifecycle is: + +1. submit a verification request +2. receive verification signals and a signed verification receipt +3. store the receipt with the workflow record +4. run later verification before downstream reliance +5. use authorized lifecycle actions when receipt state changes + ## Production Deployment Requirements Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. diff --git a/wiki/What-is-TrustSignal.md b/wiki/What-is-TrustSignal.md index b9c1add..583fa0e 100644 --- a/wiki/What-is-TrustSignal.md +++ b/wiki/What-is-TrustSignal.md @@ -13,13 +13,21 @@ ## Problem -Many workflow systems can show that an artifact was collected or reviewed. Fewer can later verify that the same artifact is still the one tied to the recorded decision. In high-stakes workflows, that creates an attack surface around tampered evidence, provenance loss, artifact substitution, and stale evidence in later audit or review paths. +Many workflow systems can show that an artifact was collected or reviewed. Fewer can later verify that the same artifact is still the one tied to the recorded decision. In high-stakes workflows, that creates attack surfaces around evidence tampering after collection, artifact substitution attacks, provenance loss in compliance workflows, stale evidence during audit review, and unverifiable documentation chains. + +High-loss environments create incentives for those attack paths because the challenge usually appears during downstream review, not at the original moment of collection. ## Integrity Model TrustSignal is evidence integrity infrastructure. It provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification for existing workflows. -## Evaluator Path +## Demo + +The fastest local evaluator path is the 5-minute developer trial: + +- [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) + +## Integration The evaluator and demo path in this repository is a deliberate evaluator path. It is designed to show the verification lifecycle safely before production integration requirements are fully configured. @@ -38,7 +46,7 @@ The upstream platform remains the system of record. TrustSignal adds an integrit Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. -## Technical Detail +## Technical Details At a high level, the public verification lifecycle is: From fe0d510dcc1d71e54db79b0cc36dd26d03949416 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 16:56:12 -0500 Subject: [PATCH 06/25] Add canonical lifecycle diagram --- README.md | 16 +++--- docs/README.md | 10 ++-- docs/partner-eval/overview.md | 8 +-- docs/verification-lifecycle.md | 80 ++++++++++++++++++++++++++++++ wiki/API-Overview.md | 6 ++- wiki/Home.md | 6 ++- wiki/Quick-Verification-Example.md | 12 +++-- 7 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 docs/verification-lifecycle.md diff --git a/README.md b/README.md index 541af69..b1deba6 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,15 @@ High-stakes document and evidence workflows create an attack surface after colle Those risks matter in audit, compliance, partner-review, and trust-sensitive workflows because the evidence is often challenged after collection rather than at the moment it first entered the system. TrustSignal is designed for workflows where later auditability matters because the artifact, its provenance, or the surrounding workflow record may be questioned later. -## Integrity Model +## Verification Lifecycle -TrustSignal addresses that problem class by accepting a verification request, evaluating it against configured checks, and issuing a signed verification receipt. The receipt gives downstream systems a stable handle for later verification, receipt retrieval, lifecycle checks, and verifiable provenance. +The canonical lifecycle diagram and trust-boundary view are documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). + +TrustSignal accepts a verification request, returns verification signals, issues a signed verification receipt, and supports later verification against stored receipt state so downstream teams can detect artifact tampering, evidence provenance loss, or stale records during audit review. + +## Demo + +The fastest evaluator path is the local 5-minute developer trial: TrustSignal provides: @@ -28,10 +34,6 @@ TrustSignal provides: - later verification capability - existing workflow integration through the public API boundary -## Demo - -The fastest evaluator path is the local 5-minute developer trial: - ```bash npm install npm run demo @@ -47,7 +49,7 @@ It shows the full lifecycle in one run: See [demo/README.md](/Users/christopher/Projects/trustsignal/demo/README.md). -## Integration +## Integration Model Start here if you are evaluating the public verification lifecycle: diff --git a/docs/README.md b/docs/README.md index 4b2b23e..02aaf80 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,9 @@ This folder is organized into active, canonical documents and archived historica TrustSignal documentation is written for evaluators and implementers working in workflows where later auditability matters. The main attack surface is not only bad data at intake, but also tampered evidence, provenance loss, artifact substitution, and stale evidence that cannot be verified later. -## Integrity Model +## Verification Lifecycle + +The canonical lifecycle and trust-boundary diagrams are documented in [verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal is evidence integrity infrastructure. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflow integration. @@ -16,9 +18,9 @@ Start with the local developer trial if you want the fastest technical evaluatio - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) -The demo shows artifact hashing, verification, signed receipt issuance, later verification, and tampered artifact mismatch detection without external services. +The demo shows artifact hashing, verification, signed verification receipt issuance, later verification, and tampered artifact mismatch detection without external services. -## Integration +## Integration Model Start here if you want to evaluate the public verification lifecycle quickly: @@ -36,8 +38,6 @@ Golden path: 3. retrieve the stored receipt 4. run later verification -## Technical Details - ## Integration Fit The evaluator and demo paths are deliberate evaluator paths. They show the verification lifecycle safely before production integration and do not remove production security requirements. diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 5fa7041..15b091f 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -4,9 +4,11 @@ Teams often have a workflow record that says an artifact was reviewed, approved, or submitted, but they cannot easily prove later that the same artifact is still the one tied to that decision. In high-loss and highly scrutinized workflows, that creates an attack surface around tampered evidence, provenance loss, artifact substitution, and stale evidence in later review paths. -## Integrity Model +## Verification Lifecycle -TrustSignal is evidence integrity infrastructure. It acts as an integrity layer for existing workflows by accepting a verification request, returning verification signals, and issuing signed verification receipts with verifiable provenance metadata for later verification. +The canonical lifecycle diagram and trust-boundary diagram are documented in [../verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). + +TrustSignal is evidence integrity infrastructure. It acts as an integrity layer for existing workflows by accepting a verification request, returning verification signals, issuing signed verification receipts, and supporting later verification during audit review. TrustSignal is designed to support: @@ -21,7 +23,7 @@ Start with the local developer trial when you want the shortest path to the veri - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) -## Integration +## Integration Model Start with these evaluator assets: diff --git a/docs/verification-lifecycle.md b/docs/verification-lifecycle.md new file mode 100644 index 0000000..be3568e --- /dev/null +++ b/docs/verification-lifecycle.md @@ -0,0 +1,80 @@ +# TrustSignal Verification Lifecycle + +TrustSignal is evidence integrity infrastructure for existing workflows. The verification lifecycle below shows the externally visible flow for producing verification signals, issuing signed verification receipts, and supporting later verification without exposing private verification engine internals. + +## Lifecycle Diagram + +```mermaid +flowchart TD + A["Artifact or Evidence"] + B["Verification Request"] + C["TrustSignal Verification Engine"] + D["Verification Result"] + E["Signed Verification Receipt"] + F["Receipt Storage"] + G["Later Verification / Audit"] + H["Tamper Detection"] + + A --> B + B --> C + C --> D + D --> E + E --> F + F --> G + G --> H +``` + +## Step Explanations + +### 1. Artifact or Evidence + +An external workflow collects or references an artifact that needs integrity-aware verification. This can be a document, evidence packet, or another workflow artifact that may be challenged later. + +### 2. Verification Request + +The workflow submits a verification request through the TrustSignal API boundary. The request binds the artifact context and provenance fields that downstream teams may need during later review. + +### 3. TrustSignal Verification Engine + +TrustSignal evaluates the request within the private verification environment. Public documentation does not expose internal proof systems, signing infrastructure, or service topology. + +### 4. Verification Result + +The engine returns verification signals that describe the outcome of the verification request. These signals are meant for downstream workflow logic, storage, and review. + +### 5. Signed Verification Receipt + +TrustSignal issues a signed verification receipt that captures the verification outcome and verifiable provenance for later verification. + +### 6. Receipt Storage + +The external workflow stores the receipt alongside its own record. TrustSignal does not replace the system of record; it adds integrity-layer outputs that the system of record can retain. + +### 7. Later Verification / Audit + +Before relying on the earlier result during audit review, partner review, or another high-loss workflow step, the workflow can request later verification against the stored receipt state. + +### 8. Tamper Detection + +If the current artifact or stored state no longer matches the receipt-bound record, later verification produces a mismatch signal that exposes tampering, substitution, or provenance drift. + +## Trust Boundary Diagram + +```mermaid +flowchart TD + A["External Workflow / Partner System"] + B["TrustSignal API Gateway"] + C["Private Verification Engine"] + D["Verification Result + Signed Receipt"] + + A --> B + B --> C + C --> D +``` + +## Boundary Explanation + +- The external workflow or partner system remains the system of record. +- The TrustSignal API Gateway is the public integration boundary for verification and later verification requests. +- The private verification engine remains non-public. +- The public outputs are verification signals, signed verification receipts, and verifiable provenance suitable for later verification. diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md index aace22a..18fb696 100644 --- a/wiki/API-Overview.md +++ b/wiki/API-Overview.md @@ -15,7 +15,9 @@ Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. The relevant attack surface includes evidence tampering after collection, artifact substitution attacks, provenance loss in compliance workflows, stale evidence during audit review, and documentation chains that cannot be verified later. -## Integrity Model +## Verification Lifecycle + +The canonical lifecycle diagram is documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal exposes a public verification lifecycle centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. @@ -25,7 +27,7 @@ Start with the local developer trial for the fastest lifecycle walkthrough: - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) -## Integration +## Integration Model Start here to try the public lifecycle: diff --git a/wiki/Home.md b/wiki/Home.md index 3899a4d..0ff9b5b 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -19,7 +19,9 @@ TrustSignal is built for workflows where evidence can be challenged after collec High-loss environments create incentives for these attack paths because downstream reviewers often must rely on artifacts long after the original collection event. -## Integrity Model +## Verification Lifecycle + +The canonical lifecycle diagram is documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability as an integrity layer for an existing system of record. @@ -35,7 +37,7 @@ TrustSignal provides signed verification receipts, verification signals, verifia - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) -## Integration +## Integration Model Use the evaluator docs when you want to see the verification lifecycle before production integration detail: diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md index a2eb77a..1283e73 100644 --- a/wiki/Quick-Verification-Example.md +++ b/wiki/Quick-Verification-Example.md @@ -15,11 +15,13 @@ This example is for partner engineers who want the smallest realistic TrustSignal flow that shows what goes in, what comes back, and how later verification works. It is intended for workflows where tampered evidence, provenance loss, artifact substitution, and stale evidence matter after collection. -## Integrity Model +## Verification Lifecycle -The example uses the current integration-facing lifecycle to create a verification, return verification signals plus a signed verification receipt, and later verify stored receipt state. +The canonical lifecycle diagram is documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). -## Evaluator Path +This example uses the current integration-facing lifecycle to create a verification, return verification signals plus a signed verification receipt, store the receipt with the workflow record, and later verify stored receipt state during audit review. + +## Demo Start here for the full evaluator path: @@ -28,7 +30,7 @@ Start here for the full evaluator path: - [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) - [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) -## Integration Fit +## Integration Model This example is a deliberate evaluator path. It is designed to show the verification lifecycle before production authentication, signing, and environment requirements are fully configured. @@ -36,7 +38,7 @@ This example is a deliberate evaluator path. It is designed to show the verifica Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. -## Technical Detail +## Technical Details ```mermaid sequenceDiagram From d3d908735d9612833b118967583efcaca4f35438 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 17:11:35 -0500 Subject: [PATCH 07/25] feat: add evaluator start-here and API trial path --- docs/partner-eval/overview.md | 2 + docs/partner-eval/start-here.md | 76 +++++++++++++++++++ docs/partner-eval/try-the-api.md | 75 ++++++++++++++++++ examples/verification-receipt.json | 8 +- examples/verification-request.json | 6 +- examples/verification-response.json | 2 +- ...TrustSignal.local.postman_environment.json | 6 ++ postman/TrustSignal.postman_collection.json | 44 +++++++++-- 8 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 docs/partner-eval/start-here.md create mode 100644 docs/partner-eval/try-the-api.md diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 15b091f..d83bc15 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -22,6 +22,8 @@ TrustSignal is designed to support: Start with the local developer trial when you want the shortest path to the verification lifecycle: - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) +- [Evaluator start here](/Users/christopher/Projects/trustsignal/docs/partner-eval/start-here.md) +- [Try the API](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) ## Integration Model diff --git a/docs/partner-eval/start-here.md b/docs/partner-eval/start-here.md new file mode 100644 index 0000000..ae410fc --- /dev/null +++ b/docs/partner-eval/start-here.md @@ -0,0 +1,76 @@ +# TrustSignal Evaluator Start Here + +This is the canonical evaluator entry point for partner engineers reviewing TrustSignal. + +## 1. What TrustSignal Does + +TrustSignal is evidence integrity infrastructure for existing workflow integration. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance, and later verification capability without replacing the system of record. + +TrustSignal provides: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification capability + +TrustSignal does not provide: + +- legal determinations +- compliance certification +- fraud adjudication +- replacement for the system of record + +## 2. The Verification Lifecycle + +Start with the canonical lifecycle and trust-boundary diagrams in [verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). + +The public lifecycle is: + +1. submit a verification request +2. receive verification signals and a signed verification receipt +3. store the receipt with the workflow record +4. run later verification before downstream reliance +5. detect tampering, substitution, or provenance drift through later verification + +## 3. What You Can Evaluate In 5 Minutes + +Use the local evaluator and public API artifacts to confirm: + +- whether the public API returns verification signals you can store in an existing workflow +- whether signed verification receipts are easy to retrieve and inspect +- whether later verification is explicit and easy to re-run during audit review +- whether the contract exposes verifiable provenance without exposing internal implementation details + +Start here: + +- [overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [try-the-api.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) +- [demo/README.md](/Users/christopher/Projects/trustsignal/demo/README.md) + +## 4. Public API Contract + +- [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) +- [TrustSignal.postman_collection.json](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) +- [TrustSignal.local.postman_environment.json](/Users/christopher/Projects/trustsignal/postman/TrustSignal.local.postman_environment.json) + +The public evaluator path uses the existing `/api/v1/*` contract only. + +## 5. Example Payloads + +- [examples/verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) +- [examples/verification-response.json](/Users/christopher/Projects/trustsignal/examples/verification-response.json) +- [examples/verification-receipt.json](/Users/christopher/Projects/trustsignal/examples/verification-receipt.json) +- [examples/verification-status.json](/Users/christopher/Projects/trustsignal/examples/verification-status.json) + +## 6. Security / Claims Boundary + +- [security-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/security-summary.md) +- [claims-boundary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/claims-boundary.md) + +Public evaluator materials intentionally do not expose proof internals, circuit identifiers, model outputs, signing infrastructure specifics, internal service topology, witness or prover details, or registry scoring algorithms. + +## 7. Where To Go Next + +- [integration-model.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/integration-model.md) +- [api-playground.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [quickstart.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) diff --git a/docs/partner-eval/try-the-api.md b/docs/partner-eval/try-the-api.md new file mode 100644 index 0000000..d6de701 --- /dev/null +++ b/docs/partner-eval/try-the-api.md @@ -0,0 +1,75 @@ +# Try The TrustSignal API + +This page is the copy-paste API trial path for the public evaluator contract. + +## 1. Submit A Verification Request + +Request body: [verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/verify" \ + -H "Content-Type: application/json" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ + --data @examples/verification-request.json +``` + +Sample response: [verification-response.json](/Users/christopher/Projects/trustsignal/examples/verification-response.json) + +```json +{ + "receiptVersion": "2.0", + "decision": "ALLOW", + "reasons": ["receipt issued"], + "receiptId": "623e0b54-87b3-42b7-bc89-65fae0ad8d5e", + "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", + "receiptSignature": { + "alg": "EdDSA", + "kid": "trustsignal-current", + "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" + }, + "anchor": { + "status": "PENDING", + "subjectDigest": "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112", + "subjectVersion": "trustsignal.anchor_subject.v1" + }, + "revocation": { + "status": "ACTIVE" + } +} +``` + +## 2. Retrieve The Stored Receipt + +Receipt example: [verification-receipt.json](/Users/christopher/Projects/trustsignal/examples/verification-receipt.json) + +```bash +curl "$TRUSTSIGNAL_BASE_URL/api/v1/receipt/$RECEIPT_ID" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +## 3. Run Later Verification + +Status example: [verification-status.json](/Users/christopher/Projects/trustsignal/examples/verification-status.json) + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/receipt/$RECEIPT_ID/verify" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +## 4. Optional Public Lifecycle Actions + +If your evaluation includes provenance-state review: + +```bash +curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/anchor/$RECEIPT_ID" \ + -H "x-api-key: $TRUSTSIGNAL_API_KEY" +``` + +Revocation is part of the public contract, but it requires issuer authorization headers in addition to the API key. Use the Postman collection for the full request template. + +## Production Readiness + +- Authentication: use `x-api-key` with the scopes required for verify, read, anchor, or revoke operations. +- Environment configuration: separate local, staging, and production base URLs, API keys, and lifecycle identifiers. +- Lifecycle monitoring: monitor receipt retrieval, lifecycle state changes, and later verification outcomes in the surrounding workflow. +- Verification checks before relying on prior results: run later verification before audit review, handoff, or another high-trust workflow step. diff --git a/examples/verification-receipt.json b/examples/verification-receipt.json index 857510c..fb44e53 100644 --- a/examples/verification-receipt.json +++ b/examples/verification-receipt.json @@ -4,7 +4,7 @@ "reasons": [ "receipt issued" ], - "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptId": "623e0b54-87b3-42b7-bc89-65fae0ad8d5e", "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", "receiptSignature": { "alg": "EdDSA", @@ -21,7 +21,7 @@ }, "receipt": { "receiptVersion": "2.0", - "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptId": "623e0b54-87b3-42b7-bc89-65fae0ad8d5e", "createdAt": "2026-03-12T15:24:01.000Z", "policyProfile": "CONTROL_CC_001", "inputsCommitment": "0x2dded9c1b5c4c6d91df58a1b1793cb527f2b0cf5ddaf447f5b7d9839f7ab7d01", @@ -43,6 +43,6 @@ "signature": "eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ" } }, - "canonicalReceipt": "{\"checks\":[{\"checkId\":\"registry.status\",\"details\":\"Source responded with a current record\",\"status\":\"PASS\"}],\"createdAt\":\"2026-03-12T15:24:01.000Z\",\"decision\":\"ALLOW\",\"inputsCommitment\":\"0x2dded9c1b5c4c6d91df58a1b1793cb527f2b0cf5ddaf447f5b7d9839f7ab7d01\",\"policyProfile\":\"CONTROL_CC_001\",\"reasons\":[\"receipt issued\"],\"receiptId\":\"2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a\",\"receiptVersion\":\"2.0\"}", - "pdfUrl": "/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a/pdf" + "canonicalReceipt": "{\"checks\":[{\"checkId\":\"registry.status\",\"details\":\"Source responded with a current record\",\"status\":\"PASS\"}],\"createdAt\":\"2026-03-12T15:24:01.000Z\",\"decision\":\"ALLOW\",\"inputsCommitment\":\"0x2dded9c1b5c4c6d91df58a1b1793cb527f2b0cf5ddaf447f5b7d9839f7ab7d01\",\"policyProfile\":\"CONTROL_CC_001\",\"reasons\":[\"receipt issued\"],\"receiptId\":\"623e0b54-87b3-42b7-bc89-65fae0ad8d5e\",\"receiptVersion\":\"2.0\"}", + "pdfUrl": "/api/v1/receipt/623e0b54-87b3-42b7-bc89-65fae0ad8d5e/pdf" } diff --git a/examples/verification-request.json b/examples/verification-request.json index 2e446f0..3823f16 100644 --- a/examples/verification-request.json +++ b/examples/verification-request.json @@ -1,5 +1,5 @@ { - "bundleId": "verification-2026-03-12-001", + "bundleId": "verification-2026-03-12-start-here", "transactionType": "deed_transfer", "ron": { "provider": "source-system", @@ -14,9 +14,9 @@ "profile": "CONTROL_CC_001" }, "property": { - "parcelId": "PARCEL-EXAMPLE-1001", + "parcelId": "PARCEL-EVAL-1001", "county": "Cook", "state": "IL" }, - "timestamp": "2026-03-12T15:24:00.000Z" + "timestamp": "2026-03-12T18:24:00.000Z" } diff --git a/examples/verification-response.json b/examples/verification-response.json index 83eba8a..9a968de 100644 --- a/examples/verification-response.json +++ b/examples/verification-response.json @@ -4,7 +4,7 @@ "reasons": [ "receipt issued" ], - "receiptId": "2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a", + "receiptId": "623e0b54-87b3-42b7-bc89-65fae0ad8d5e", "receiptHash": "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04", "receiptSignature": { "alg": "EdDSA", diff --git a/postman/TrustSignal.local.postman_environment.json b/postman/TrustSignal.local.postman_environment.json index d2213ef..4f38fbe 100644 --- a/postman/TrustSignal.local.postman_environment.json +++ b/postman/TrustSignal.local.postman_environment.json @@ -26,6 +26,12 @@ "type": "default", "enabled": true }, + { + "key": "pdf_url", + "value": "replace-after-verify", + "type": "default", + "enabled": true + }, { "key": "issuer_id", "value": "replace-for-revoke-tests", diff --git a/postman/TrustSignal.postman_collection.json b/postman/TrustSignal.postman_collection.json index b2100c3..4325536 100644 --- a/postman/TrustSignal.postman_collection.json +++ b/postman/TrustSignal.postman_collection.json @@ -1,7 +1,7 @@ { "info": { "name": "TrustSignal Evaluator Trial Path", - "description": "Public evaluator collection for the TrustSignal verification lifecycle. This collection demonstrates how to submit a verification request, receive signed verification receipts and verification signals, retrieve the stored receipt, run later verification, and review authorized lifecycle actions using the public contract only.", + "description": "Public evaluator collection for the TrustSignal verification lifecycle. Start with docs/partner-eval/start-here.md, then use this collection to submit a verification request, receive signed verification receipts and verification signals, retrieve the stored receipt, run later verification, and review authorized lifecycle actions using the public contract only.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ @@ -21,6 +21,10 @@ "key": "receipt_id", "value": "{{receipt_id}}" }, + { + "key": "pdf_url", + "value": "{{pdf_url}}" + }, { "key": "issuer_id", "value": "{{issuer_id}}" @@ -46,6 +50,9 @@ "if (response.receiptId) {", " pm.environment.set('verification_id', response.receiptId);", " pm.environment.set('receipt_id', response.receiptId);", + "}", + "if (response.receiptVersion && response.receiptId) {", + " pm.environment.set('pdf_url', pm.variables.get('base_url') + '/api/v1/receipt/' + response.receiptId + '/pdf');", "}" ], "type": "text/javascript" @@ -66,7 +73,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"bundleId\": \"verification-2026-03-12-001\",\n \"transactionType\": \"deed_transfer\",\n \"ron\": {\n \"provider\": \"source-system\",\n \"notaryId\": \"NOTARY-EXAMPLE-01\",\n \"commissionState\": \"IL\",\n \"sealPayload\": \"simulated-seal-payload\"\n },\n \"doc\": {\n \"docHash\": \"0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa\"\n },\n \"policy\": {\n \"profile\": \"CONTROL_CC_001\"\n },\n \"property\": {\n \"parcelId\": \"PARCEL-EXAMPLE-1001\",\n \"county\": \"Cook\",\n \"state\": \"IL\"\n },\n \"timestamp\": \"2026-03-12T15:24:00.000Z\"\n}" + "raw": "{\n \"bundleId\": \"verification-2026-03-12-start-here\",\n \"transactionType\": \"deed_transfer\",\n \"ron\": {\n \"provider\": \"source-system\",\n \"notaryId\": \"NOTARY-EXAMPLE-01\",\n \"commissionState\": \"IL\",\n \"sealPayload\": \"simulated-seal-payload\"\n },\n \"doc\": {\n \"docHash\": \"0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa\"\n },\n \"policy\": {\n \"profile\": \"CONTROL_CC_001\"\n },\n \"property\": {\n \"parcelId\": \"PARCEL-EVAL-1001\",\n \"county\": \"Cook\",\n \"state\": \"IL\"\n },\n \"timestamp\": \"2026-03-12T18:24:00.000Z\"\n}" }, "description": "Submit a public verification request from an existing workflow. The response should include verification signals, a signed verification receipt, and public-safe verifiable provenance metadata for later verification.", "url": { @@ -110,7 +117,34 @@ "response": [] }, { - "name": "3. Run Later Verification", + "name": "3. Download Receipt PDF", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{api_key}}" + } + ], + "description": "Download the PDF rendering of the stored verification receipt when the evaluator wants the receipt-ready artifact.", + "url": { + "raw": "{{base_url}}/api/v1/receipt/{{receipt_id}}/pdf", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "v1", + "receipt", + "{{receipt_id}}", + "pdf" + ] + } + }, + "response": [] + }, + { + "name": "4. Run Later Verification", "request": { "method": "POST", "header": [ @@ -137,7 +171,7 @@ "response": [] }, { - "name": "4. Revoke Receipt", + "name": "5. Revoke Receipt", "request": { "method": "POST", "header": [ @@ -176,7 +210,7 @@ "response": [] }, { - "name": "5. Review Provenance State", + "name": "6. Review Provenance State", "request": { "method": "POST", "header": [ From b2687e4845765e2daf088b8c02ec808dcd2da706 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 17:23:30 -0500 Subject: [PATCH 08/25] feat: add benchmark harness and fresh evaluator metrics --- README.md | 13 + bench/README.md | 44 ++ bench/fixtures/clean-artifact.txt | 5 + bench/fixtures/tampered-artifact.txt | 5 + bench/results/latest.json | 500 ++++++++++++ bench/results/latest.md | 56 ++ bench/run-bench.ts | 1094 ++++++++++++++++++++++++++ bench/scenarios.md | 57 ++ docs/partner-eval/overview.md | 13 + docs/partner-eval/try-the-api.md | 12 + docs/verification-lifecycle.md | 12 + wiki/Quick-Verification-Example.md | 12 + 12 files changed, 1823 insertions(+) create mode 100644 bench/README.md create mode 100644 bench/fixtures/clean-artifact.txt create mode 100644 bench/fixtures/tampered-artifact.txt create mode 100644 bench/results/latest.json create mode 100644 bench/results/latest.md create mode 100644 bench/run-bench.ts create mode 100644 bench/scenarios.md diff --git a/README.md b/README.md index b1deba6..327a1c9 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,19 @@ npm run typecheck npm run build ``` +## Current Evaluator Metrics + +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: + +- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` +- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` +- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` +- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` +- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` +- repeated-run stability for the same artifact payload: mean `3.24 ms`, median `3.21 ms`, p95 `4.04 ms` + +This is a recent local evaluator run against the current `/api/v1/*` lifecycle with a temporary local PostgreSQL instance. It is a benchmark snapshot for evaluation and regression tracking, not a production guarantee or SLA. + ## Documentation Map - [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) diff --git a/bench/README.md b/bench/README.md new file mode 100644 index 0000000..7417b53 --- /dev/null +++ b/bench/README.md @@ -0,0 +1,44 @@ +# TrustSignal Benchmark Harness + +This harness measures the current public evaluator lifecycle without changing public endpoint names, SDK behavior, or core verification logic. + +## What It Covers + +- verification request latency via `POST /api/v1/verify` +- signed receipt generation latency using the same receipt build and signing primitives used by the evaluator flow +- receipt lookup latency via `GET /api/v1/receipt/:receiptId` +- later verification latency via `POST /api/v1/receipt/:receiptId/verify` +- tampered artifact detection latency during evaluator submission +- repeated-run stability for the same artifact payload +- evaluator-relevant negative cases such as bad auth, malformed payloads, and a safe dependency-failure path + +## Run + +```bash +npx tsx bench/run-bench.ts +``` + +Useful variants: + +```bash +npx tsx bench/run-bench.ts --scenario clean --runs 15 +npx tsx bench/run-bench.ts --scenario tampered --runs 15 +npx tsx bench/run-bench.ts --scenario lookup --runs 15 +npx tsx bench/run-bench.ts --scenario batch --batch-size 10 +``` + +## Output + +The harness writes: + +- [latest.json](/Users/christopher/Projects/trustsignal/bench/results/latest.json) +- [latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) + +The JSON contains raw timings plus aggregate metrics. The Markdown report is the public-safe evaluator summary for docs. + +## Reproducibility Notes + +- The harness starts a temporary local PostgreSQL instance and tears it down after the run. +- It targets the real local `/api/v1/*` evaluator routes through Fastify injection, so it exercises the same request validation, auth checks, persistence, receipt issuance, and later-verification logic used by the current evaluator path. +- It uses local fixture artifacts from [bench/fixtures](/Users/christopher/Projects/trustsignal/bench/fixtures) to keep clean and tampered runs deterministic. +- Current metrics are local benchmark snapshots, not production guarantees. diff --git a/bench/fixtures/clean-artifact.txt b/bench/fixtures/clean-artifact.txt new file mode 100644 index 0000000..e3a251b --- /dev/null +++ b/bench/fixtures/clean-artifact.txt @@ -0,0 +1,5 @@ +Prepared By: TrustSignal Benchmark Harness +Mail To: Evaluator Review Queue +Property Address: 100 Integrity Way, Demo City +Legal Description: Lot 1, Block A, TrustSignal Research Park +Narrative: baseline artifact bytes for signed receipt issuance and later verification. diff --git a/bench/fixtures/tampered-artifact.txt b/bench/fixtures/tampered-artifact.txt new file mode 100644 index 0000000..551c0ac --- /dev/null +++ b/bench/fixtures/tampered-artifact.txt @@ -0,0 +1,5 @@ +Prepared By: TrustSignal Benchmark Harness +Mail To: Evaluator Review Queue +Property Address: 999 Altered Address, Demo City +Legal Description: Lot 9, Block Z, Modified After Declared Hash +Narrative: artifact bytes intentionally changed after the original declared hash was established. diff --git a/bench/results/latest.json b/bench/results/latest.json new file mode 100644 index 0000000..c0307e2 --- /dev/null +++ b/bench/results/latest.json @@ -0,0 +1,500 @@ +{ + "generatedAt": "2026-03-12T22:22:06.846Z", + "command": "npx tsx bench/run-bench.ts --scenario all --runs 15 --batch-size 10", + "environment": { + "node": "v22.14.0", + "platform": "darwin", + "arch": "arm64", + "hostname": "Christophers-Mac-mini.local", + "tempDatabase": { + "engine": "postgresql", + "port": 63688, + "dbName": "trustsignal_bench" + } + }, + "harness": { + "scenario": "all", + "runs": 15, + "batchSize": 10 + }, + "metrics": { + "verificationRequestLatency": { + "count": 15, + "minMs": 3.19, + "maxMs": 19.57, + "meanMs": 5.06, + "medianMs": 3.78, + "p95Ms": 19.57 + }, + "signedReceiptGenerationLatency": { + "count": 15, + "minMs": 0.28, + "maxMs": 0.9, + "meanMs": 0.38, + "medianMs": 0.32, + "p95Ms": 0.9 + }, + "laterVerificationLatency": { + "count": 15, + "minMs": 0.7, + "maxMs": 1.07, + "meanMs": 0.76, + "medianMs": 0.72, + "p95Ms": 1.07 + }, + "statusLookupLatency": { + "count": 15, + "minMs": 0.56, + "maxMs": 0.71, + "meanMs": 0.6, + "medianMs": 0.57, + "p95Ms": 0.71 + }, + "tamperedArtifactDetectionLatency": { + "count": 15, + "minMs": 4.69, + "maxMs": 42.84, + "meanMs": 8.02, + "medianMs": 5.01, + "p95Ms": 42.84 + }, + "repeatedRunStability": { + "count": 15, + "minMs": 3, + "maxMs": 4.04, + "meanMs": 3.24, + "medianMs": 3.21, + "p95Ms": 4.04 + } + }, + "scenarios": [ + { + "scenario": "clean", + "purpose": "Measure end-to-end clean artifact verification through POST /api/v1/verify.", + "command": "npx tsx bench/run-bench.ts --scenario clean --runs 15", + "metricsCaptured": [ + "verification request latency", + "signed receipt generation latency" + ], + "expectedOutcome": "HTTP 200 with receiptId, receiptHash, and receiptSignature present.", + "timingsMs": [ + 19.57, + 5.58, + 4.46, + 4.2, + 5.58, + 3.9, + 3.55, + 3.51, + 4.4, + 3.78, + 3.74, + 3.48, + 3.37, + 3.19, + 3.53 + ], + "statusCodes": [ + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200 + ], + "successCount": 15, + "failureCount": 0, + "reliabilityNotes": [ + "15/15 clean verification requests returned signed receipts." + ], + "caveats": [], + "summary": { + "count": 15, + "minMs": 3.19, + "maxMs": 19.57, + "meanMs": 5.06, + "medianMs": 3.78, + "p95Ms": 19.57 + } + }, + { + "scenario": "tampered", + "purpose": "Measure latency for a tampered artifact submission where the declared hash does not match the supplied bytes.", + "command": "npx tsx bench/run-bench.ts --scenario tampered --runs 15", + "metricsCaptured": [ + "tampered artifact detection latency" + ], + "expectedOutcome": "HTTP 200 with mismatch visible in zkpAttestation.publicInputs declaredDocHash vs documentDigest.", + "timingsMs": [ + 42.84, + 11.13, + 6.03, + 4.93, + 5.01, + 4.84, + 5.01, + 4.99, + 5.61, + 5.52, + 4.85, + 4.69, + 4.72, + 5.19, + 5.01 + ], + "statusCodes": [ + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200 + ], + "successCount": 15, + "failureCount": 0, + "reliabilityNotes": [ + "15/15 tampered runs surfaced a declared hash vs observed digest mismatch." + ], + "caveats": [], + "summary": { + "count": 15, + "minMs": 4.69, + "maxMs": 42.84, + "meanMs": 8.02, + "medianMs": 5.01, + "p95Ms": 42.84 + } + }, + { + "scenario": "repeat", + "purpose": "Measure stability when the same artifact payload is verified repeatedly.", + "command": "npx tsx bench/run-bench.ts --scenario repeat --runs 15", + "metricsCaptured": [ + "repeated-run stability" + ], + "expectedOutcome": "Repeated requests continue returning HTTP 200 and signed receipts without contract drift.", + "timingsMs": [ + 3.48, + 3.21, + 4.04, + 3.42, + 3.25, + 3.24, + 3.23, + 3.09, + 3.05, + 3.09, + 3.37, + 3.11, + 3.04, + 3, + 3.03 + ], + "statusCodes": [ + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200 + ], + "successCount": 15, + "failureCount": 0, + "reliabilityNotes": [ + "15/15 repeated submissions of the same payload returned HTTP 200." + ], + "caveats": [], + "summary": { + "count": 15, + "minMs": 3, + "maxMs": 4.04, + "meanMs": 3.24, + "medianMs": 3.21, + "p95Ms": 4.04 + } + }, + { + "scenario": "lookup", + "purpose": "Measure receipt retrieval latency through GET /api/v1/receipt/:receiptId.", + "command": "npx tsx bench/run-bench.ts --scenario lookup --runs 15", + "metricsCaptured": [ + "status lookup latency" + ], + "expectedOutcome": "HTTP 200 with persisted receipt payload.", + "timingsMs": [ + 0.64, + 0.71, + 0.62, + 0.59, + 0.58, + 0.57, + 0.56, + 0.56, + 0.71, + 0.57, + 0.56, + 0.56, + 0.57, + 0.57, + 0.56 + ], + "statusCodes": [ + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200 + ], + "successCount": 15, + "failureCount": 0, + "reliabilityNotes": [ + "15/15 receipt lookup requests returned the stored receipt." + ], + "caveats": [], + "summary": { + "count": 15, + "minMs": 0.56, + "maxMs": 0.71, + "meanMs": 0.6, + "medianMs": 0.57, + "p95Ms": 0.71 + } + }, + { + "scenario": "later-verification", + "purpose": "Measure later verification latency through POST /api/v1/receipt/:receiptId/verify.", + "command": "npx tsx bench/run-bench.ts --scenario lookup --runs 15", + "metricsCaptured": [ + "later verification latency" + ], + "expectedOutcome": "HTTP 200 with verified=true, integrityVerified=true, and signatureVerified=true.", + "timingsMs": [ + 1.07, + 0.78, + 0.76, + 0.74, + 0.71, + 0.71, + 0.71, + 0.71, + 0.86, + 0.72, + 0.79, + 0.71, + 0.75, + 0.7, + 0.71 + ], + "statusCodes": [ + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200 + ], + "successCount": 15, + "failureCount": 0, + "reliabilityNotes": [ + "15/15 later verification requests returned verified=true." + ], + "caveats": [], + "summary": { + "count": 15, + "minMs": 0.7, + "maxMs": 1.07, + "meanMs": 0.76, + "medianMs": 0.72, + "p95Ms": 1.07 + } + }, + { + "scenario": "bad-auth", + "purpose": "Confirm evaluator-visible fail-closed behavior for missing or invalid API authentication.", + "command": "npx tsx bench/run-bench.ts --scenario bad-auth", + "metricsCaptured": [ + "auth failure response latency" + ], + "expectedOutcome": "Missing auth returns 401 and invalid auth returns 403.", + "timingsMs": [ + 0.23, + 0.15 + ], + "statusCodes": [ + 401, + 403 + ], + "successCount": 2, + "failureCount": 0, + "reliabilityNotes": [ + "2/2 auth-failure probes returned the expected 401 or 403 response." + ], + "caveats": [], + "summary": { + "count": 2, + "minMs": 0.15, + "maxMs": 0.23, + "meanMs": 0.19, + "medianMs": 0.19, + "p95Ms": 0.23 + } + }, + { + "scenario": "malformed", + "purpose": "Confirm malformed evaluator payloads fail early without entering the verification lifecycle.", + "command": "npx tsx bench/run-bench.ts --scenario malformed", + "metricsCaptured": [ + "payload validation failure latency" + ], + "expectedOutcome": "HTTP 400 with Invalid payload error.", + "timingsMs": [ + 0.46, + 0.39 + ], + "statusCodes": [ + 400, + 400 + ], + "successCount": 2, + "failureCount": 0, + "reliabilityNotes": [ + "2/2 malformed payload probes returned HTTP 400." + ], + "caveats": [], + "summary": { + "count": 2, + "minMs": 0.39, + "maxMs": 0.46, + "meanMs": 0.43, + "medianMs": 0.43, + "p95Ms": 0.46 + } + }, + { + "scenario": "dependency-failure", + "purpose": "Measure fail-closed behavior when an external registry dependency is unavailable without configured access.", + "command": "npx tsx bench/run-bench.ts --scenario dependency-failure", + "metricsCaptured": [ + "dependency failure response latency" + ], + "expectedOutcome": "HTTP 200 with a non-ALLOW decision reflecting compliance-gap or fail-closed handling.", + "timingsMs": [ + 13.35 + ], + "statusCodes": [ + 200 + ], + "successCount": 1, + "failureCount": 0, + "reliabilityNotes": [ + "Registry dependency failure produced a non-ALLOW decision without exposing internal dependency details." + ], + "caveats": [], + "summary": { + "count": 1, + "minMs": 13.35, + "maxMs": 13.35, + "meanMs": 13.35, + "medianMs": 13.35, + "p95Ms": 13.35 + } + }, + { + "scenario": "batch", + "purpose": "Measure sequential small-batch behavior over a short evaluator run.", + "command": "npx tsx bench/run-bench.ts --scenario batch --batch-size 10", + "metricsCaptured": [ + "small batch latency distribution" + ], + "expectedOutcome": "All 10 sequential requests return HTTP 200 with signed receipts.", + "timingsMs": [ + 3.31, + 3.09, + 3.07, + 3.26, + 3.37, + 3.35, + 3.11, + 3.57, + 3.18, + 3.09 + ], + "statusCodes": [ + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200, + 200 + ], + "successCount": 10, + "failureCount": 0, + "reliabilityNotes": [ + "10/10 batch requests returned HTTP 200." + ], + "caveats": [], + "summary": { + "count": 10, + "minMs": 3.07, + "maxMs": 3.57, + "meanMs": 3.24, + "medianMs": 3.22, + "p95Ms": 3.57 + } + } + ], + "notableFailures": [], + "caveats": [ + "tampered: The tampered scenario uses a local byte fixture to force a declared-hash mismatch. It is suitable for evaluator behavior checks, not for asserting document-parser completeness." + ] +} diff --git a/bench/results/latest.md b/bench/results/latest.md new file mode 100644 index 0000000..ad34128 --- /dev/null +++ b/bench/results/latest.md @@ -0,0 +1,56 @@ +# TrustSignal Benchmark Snapshot + +## Test Date/Time +- 2026-03-12T22:22:06.846Z + +## Environment Description +- Node: v22.14.0 +- Platform: darwin (arm64) +- Host: Christophers-Mac-mini.local +- Temp database: postgresql on 127.0.0.1:63688 +- Harness command: `npx tsx bench/run-bench.ts --scenario all --runs 15 --batch-size 10` + +## Scenarios Executed +- clean: Measure end-to-end clean artifact verification through POST /api/v1/verify. +- tampered: Measure latency for a tampered artifact submission where the declared hash does not match the supplied bytes. +- repeat: Measure stability when the same artifact payload is verified repeatedly. +- lookup: Measure receipt retrieval latency through GET /api/v1/receipt/:receiptId. +- later-verification: Measure later verification latency through POST /api/v1/receipt/:receiptId/verify. +- bad-auth: Confirm evaluator-visible fail-closed behavior for missing or invalid API authentication. +- malformed: Confirm malformed evaluator payloads fail early without entering the verification lifecycle. +- dependency-failure: Measure fail-closed behavior when an external registry dependency is unavailable without configured access. +- batch: Measure sequential small-batch behavior over a short evaluator run. + +## Timing Summary Table + +| Scenario | Count | Min (ms) | Max (ms) | Mean (ms) | Median (ms) | p95 (ms) | Success / Total | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| clean | 15 | 3.19 | 19.57 | 5.06 | 3.78 | 19.57 | 15/15 | +| tampered | 15 | 4.69 | 42.84 | 8.02 | 5.01 | 42.84 | 15/15 | +| repeat | 15 | 3 | 4.04 | 3.24 | 3.21 | 4.04 | 15/15 | +| lookup | 15 | 0.56 | 0.71 | 0.6 | 0.57 | 0.71 | 15/15 | +| later-verification | 15 | 0.7 | 1.07 | 0.76 | 0.72 | 1.07 | 15/15 | +| bad-auth | 2 | 0.15 | 0.23 | 0.19 | 0.19 | 0.23 | 2/2 | +| malformed | 2 | 0.39 | 0.46 | 0.43 | 0.43 | 0.46 | 2/2 | +| dependency-failure | 1 | 13.35 | 13.35 | 13.35 | 13.35 | 13.35 | 1/1 | +| batch | 10 | 3.07 | 3.57 | 3.24 | 3.22 | 3.57 | 10/10 | + +## Reliability Notes +- clean: 15/15 clean verification requests returned signed receipts. +- tampered: 15/15 tampered runs surfaced a declared hash vs observed digest mismatch. +- repeat: 15/15 repeated submissions of the same payload returned HTTP 200. +- lookup: 15/15 receipt lookup requests returned the stored receipt. +- later-verification: 15/15 later verification requests returned verified=true. +- bad-auth: 2/2 auth-failure probes returned the expected 401 or 403 response. +- malformed: 2/2 malformed payload probes returned HTTP 400. +- dependency-failure: Registry dependency failure produced a non-ALLOW decision without exposing internal dependency details. +- batch: 10/10 batch requests returned HTTP 200. + +## Notable Failures Or Caveats +- tampered: The tampered scenario uses a local byte fixture to force a declared-hash mismatch. It is suitable for evaluator behavior checks, not for asserting document-parser completeness. + +## What This Means For Evaluators +- This is a recent local evaluator run against the current public `/api/v1/*` lifecycle, not a production SLA. +- The numbers are most useful for comparing request classes, verifying fail-closed behavior, and spotting regressions between local validation runs. +- Clean verification, receipt lookup, and later verification can be exercised repeatedly with signed-receipt persistence under a reproducible local database setup. +- Tampered and dependency-failure scenarios surface behavior signals that evaluators can test without exposing proof internals, signer infrastructure, or internal topology. diff --git a/bench/run-bench.ts b/bench/run-bench.ts new file mode 100644 index 0000000..18a8e17 --- /dev/null +++ b/bench/run-bench.ts @@ -0,0 +1,1094 @@ +import { createHash } from 'node:crypto'; +import { Buffer } from 'node:buffer'; +import { promises as fs } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawn, execFile as execFileCallback, type ChildProcess } from 'node:child_process'; +import { promisify } from 'node:util'; +import { performance } from 'node:perf_hooks'; + +import { PrismaClient } from '@prisma/client'; +import type { FastifyInstance } from 'fastify'; + +import { loadRegistry } from '../apps/api/src/registryLoader.js'; +import { buildSecurityConfig } from '../apps/api/src/security.js'; +import { deriveNotaryWallet, signDocHash } from '../packages/core/src/synthetic.js'; +import { buildReceipt, toUnsignedReceiptPayload } from '../packages/core/src/receipt.js'; +import { signReceiptPayload } from '../packages/core/src/receiptSigner.js'; +import type { BundleInput, Receipt, TrustRegistry, VerificationResult } from '../packages/core/src/types.js'; + +const execFile = promisify(execFileCallback); + +const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..'); +const RESULTS_DIR = path.join(ROOT, 'bench', 'results'); +const FIXTURES_DIR = path.join(ROOT, 'bench', 'fixtures'); +const DEFAULT_API_KEY = 'bench-api-key'; +const DEFAULT_SCENARIO = 'all'; +const DEFAULT_RUNS = 15; +const DEFAULT_BATCH_SIZE = 10; + +type ScenarioName = + | 'clean' + | 'tampered' + | 'repeat' + | 'lookup' + | 'later-verification' + | 'bad-auth' + | 'malformed' + | 'dependency-failure' + | 'batch'; + +type CliOptions = { + scenario: ScenarioName | 'all'; + runs: number; + batchSize: number; + outputDir: string; +}; + +type TempPostgres = { + databaseUrl: string; + tmpDir: string; + pgData: string; + port: number; + dbName: string; + user: string; + started: boolean; +}; + +type TimingSummary = { + count: number; + minMs: number; + maxMs: number; + meanMs: number; + medianMs: number; + p95Ms: number; +}; + +type RawScenarioResult = { + scenario: ScenarioName; + purpose: string; + command: string; + metricsCaptured: string[]; + expectedOutcome: string; + timingsMs: number[]; + statusCodes: number[]; + successCount: number; + failureCount: number; + reliabilityNotes: string[]; + caveats: string[]; + extra?: Record; +}; + +type AggregatedScenarioResult = RawScenarioResult & { + summary: TimingSummary; +}; + +type BenchmarkOutput = { + generatedAt: string; + command: string; + environment: { + node: string; + platform: string; + arch: string; + hostname: string; + tempDatabase: { + engine: string; + port: number; + dbName: string; + }; + }; + harness: { + scenario: string; + runs: number; + batchSize: number; + }; + metrics: { + verificationRequestLatency: TimingSummary | null; + signedReceiptGenerationLatency: TimingSummary | null; + laterVerificationLatency: TimingSummary | null; + statusLookupLatency: TimingSummary | null; + tamperedArtifactDetectionLatency: TimingSummary | null; + repeatedRunStability: TimingSummary | null; + }; + scenarios: AggregatedScenarioResult[]; + notableFailures: string[]; + caveats: string[]; +}; + +type VerifyResponse = { + decision: string; + reasons: string[]; + receiptId: string; + receiptHash: string; + receiptSignature?: { + signature: string; + alg: string; + kid: string; + }; + zkpAttestation?: { + publicInputs?: { + declaredDocHash?: string; + documentDigest?: string; + }; + }; +}; + +type ReceiptDetailResponse = VerifyResponse & { + receipt: Receipt; +}; + +type StatusResponse = { + verified: boolean; + integrityVerified: boolean; + signatureVerified: boolean; +}; + +function parseArgs(argv: string[]): CliOptions { + const options: CliOptions = { + scenario: DEFAULT_SCENARIO, + runs: DEFAULT_RUNS, + batchSize: DEFAULT_BATCH_SIZE, + outputDir: RESULTS_DIR + }; + + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + const next = argv[index + 1]; + + if (arg === '--scenario' && next) { + options.scenario = next as CliOptions['scenario']; + index += 1; + continue; + } + + if (arg === '--runs' && next) { + options.runs = Math.max(1, Number.parseInt(next, 10) || DEFAULT_RUNS); + index += 1; + continue; + } + + if (arg === '--batch-size' && next) { + options.batchSize = Math.max(1, Number.parseInt(next, 10) || DEFAULT_BATCH_SIZE); + index += 1; + continue; + } + + if (arg === '--output-dir' && next) { + options.outputDir = path.resolve(ROOT, next); + index += 1; + } + } + + return options; +} + +function sha256Hex(input: Buffer): string { + return `0x${createHash('sha256').update(input).digest('hex')}`; +} + +function round(value: number): number { + return Number(value.toFixed(2)); +} + +function summarizeTimings(values: number[]): TimingSummary { + const sorted = [...values].sort((left, right) => left - right); + const count = sorted.length; + const meanMs = sorted.reduce((sum, value) => sum + value, 0) / count; + const medianIndex = Math.floor(count / 2); + const medianMs = + count % 2 === 0 + ? (sorted[medianIndex - 1] + sorted[medianIndex]) / 2 + : sorted[medianIndex]; + const p95Index = Math.max(0, Math.ceil(count * 0.95) - 1); + + return { + count, + minMs: round(sorted[0]), + maxMs: round(sorted[count - 1]), + meanMs: round(meanMs), + medianMs: round(medianMs), + p95Ms: round(sorted[p95Index]) + }; +} + +async function ensureBenchDirectories(outputDir: string) { + await fs.mkdir(outputDir, { recursive: true }); +} + +async function requireCommand(command: string) { + await execFile('sh', ['-lc', `command -v ${command}`], { cwd: ROOT }); +} + +async function detectFreePort(): Promise { + const { stdout } = await execFile('node', [ + '-e', + "const net=require('node:net');const server=net.createServer();server.listen(0,'127.0.0.1',()=>{console.log(server.address().port);server.close();});" + ], { cwd: ROOT }); + return Number.parseInt(stdout.trim(), 10); +} + +async function startTemporaryPostgres(): Promise { + for (const command of ['initdb', 'pg_ctl', 'createdb', 'psql']) { + await requireCommand(command); + } + + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'trustsignal-bench-')); + const port = await detectFreePort(); + const user = os.userInfo().username; + const dbName = 'trustsignal_bench'; + const pgData = path.join(tmpDir, 'pgdata'); + const pgLog = path.join(tmpDir, 'postgres.log'); + const databaseUrl = `postgresql://${user}@127.0.0.1:${port}/${dbName}?sslmode=disable`; + + await execFile('initdb', ['-D', pgData, '-A', 'trust', '-U', user], { cwd: ROOT }); + await execFile('pg_ctl', ['-D', pgData, '-l', pgLog, '-o', `-h 127.0.0.1 -p ${port}`, 'start'], { + cwd: ROOT + }); + await execFile('createdb', ['-h', '127.0.0.1', '-p', String(port), '-U', user, dbName], { cwd: ROOT }); + await execFile('psql', [databaseUrl, '-Atc', 'select current_database(), current_user;'], { cwd: ROOT }); + + return { + databaseUrl, + tmpDir, + pgData, + port, + dbName, + user, + started: true + }; +} + +async function stopTemporaryPostgres(pg: TempPostgres | null) { + if (!pg) return; + + try { + if (pg.started) { + await execFile('pg_ctl', ['-D', pg.pgData, 'stop', '-m', 'fast'], { cwd: ROOT }); + } + } catch { + // ignore cleanup failures + } + + await fs.rm(pg.tmpDir, { recursive: true, force: true }); +} + +async function withMeasuredInject( + app: FastifyInstance, + request: Parameters[0], + parse: (payload: string) => T +): Promise<{ elapsedMs: number; statusCode: number; body: T }> { + const started = performance.now(); + const response = await app.inject(request); + const elapsedMs = performance.now() - started; + return { + elapsedMs: round(elapsedMs), + statusCode: response.statusCode, + body: parse(response.body) + }; +} + +async function loadFixtureText(fileName: string): Promise { + return fs.readFile(path.join(FIXTURES_DIR, fileName)); +} + +async function buildBundle( + registry: TrustRegistry, + artifactBuffer: Buffer, + options: { + bundleId: string; + parcelId: string; + declaredDocHash?: string; + includePdfBase64?: boolean; + } +): Promise { + const notary = registry.notaries[0]; + const provider = registry.ronProviders.find((entry) => entry.status === 'ACTIVE') || registry.ronProviders[0]; + if (!notary || !provider) { + throw new Error('registry_missing_notary_or_provider'); + } + + const declaredDocHash = options.declaredDocHash || sha256Hex(artifactBuffer); + const sealPayload = await signDocHash(deriveNotaryWallet(notary.id), declaredDocHash); + + return { + bundleId: options.bundleId, + transactionType: 'warranty', + ron: { + provider: provider.id, + notaryId: notary.id, + commissionState: notary.commissionState, + sealPayload, + sealScheme: 'SIM-ECDSA-v1' + }, + doc: { + docHash: declaredDocHash, + ...(options.includePdfBase64 === false + ? {} + : { pdfBase64: artifactBuffer.toString('base64') }) + }, + property: { + parcelId: options.parcelId, + county: 'Demo County', + state: notary.commissionState + }, + policy: { + profile: `STANDARD_${notary.commissionState}` + }, + timestamp: '2026-03-12T12:00:00.000Z' + }; +} + +async function seedBaselineData(prisma: PrismaClient, registry: TrustRegistry) { + const notary = registry.notaries[0]; + if (!notary) { + throw new Error('registry_has_no_notaries'); + } + + await prisma.countyRecord.upsert({ + where: { parcelId: 'BENCH-PARCEL-001' }, + update: { county: 'Demo County', state: notary.commissionState, active: true }, + create: { + parcelId: 'BENCH-PARCEL-001', + county: 'Demo County', + state: notary.commissionState, + active: true + } + }); + + await prisma.countyRecord.upsert({ + where: { parcelId: 'BENCH-PARCEL-002' }, + update: { county: 'Demo County', state: notary.commissionState, active: true }, + create: { + parcelId: 'BENCH-PARCEL-002', + county: 'Demo County', + state: notary.commissionState, + active: true + } + }); +} + +async function scenarioClean( + app: FastifyInstance, + cleanBundle: BundleInput, + securityCommandBase: string, + runs: number +): Promise<{ scenario: RawScenarioResult; signingTimings: number[] }> { + const timingsMs: number[] = []; + const statusCodes: number[] = []; + const signingTimings: number[] = []; + const reliabilityNotes: string[] = []; + const caveats: string[] = []; + let successCount = 0; + let failureCount = 0; + + const securityConfig = buildSecurityConfig(); + + for (let index = 0; index < runs; index += 1) { + const bundle = { ...cleanBundle, bundleId: `BENCH-CLEAN-${index + 1}` }; + const verify = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: bundle + }, + (payload) => JSON.parse(payload) as VerifyResponse + ); + + timingsMs.push(verify.elapsedMs); + statusCodes.push(verify.statusCode); + + if (verify.statusCode === 200 && verify.body.receiptId && verify.body.receiptSignature) { + successCount += 1; + + const detail = await withMeasuredInject( + app, + { + method: 'GET', + url: `/api/v1/receipt/${verify.body.receiptId}`, + headers: { 'x-api-key': DEFAULT_API_KEY } + }, + (payload) => JSON.parse(payload) as ReceiptDetailResponse + ); + + const receipt = detail.body.receipt; + const verificationLike: VerificationResult = { + decision: receipt.decision, + reasons: receipt.reasons, + riskScore: receipt.riskScore, + checks: receipt.checks + }; + const started = performance.now(); + const rebuiltReceipt = buildReceipt(bundle, verificationLike, 'deed-shield', { + fraudRisk: receipt.fraudRisk, + zkpAttestation: receipt.zkpAttestation + }); + await signReceiptPayload( + toUnsignedReceiptPayload(rebuiltReceipt), + securityConfig.receiptSigning.current + ); + signingTimings.push(round(performance.now() - started)); + } else { + failureCount += 1; + } + } + + if (failureCount > 0) { + caveats.push('One or more clean verification runs did not return HTTP 200 with a signed receipt.'); + } + reliabilityNotes.push(`${successCount}/${runs} clean verification requests returned signed receipts.`); + + return { + scenario: { + scenario: 'clean', + purpose: 'Measure end-to-end clean artifact verification through POST /api/v1/verify.', + command: `${securityCommandBase} --scenario clean --runs ${runs}`, + metricsCaptured: ['verification request latency', 'signed receipt generation latency'], + expectedOutcome: 'HTTP 200 with receiptId, receiptHash, and receiptSignature present.', + timingsMs, + statusCodes, + successCount, + failureCount, + reliabilityNotes, + caveats + }, + signingTimings + }; +} + +async function scenarioLookup( + app: FastifyInstance, + cleanBundle: BundleInput, + securityCommandBase: string, + runs: number +): Promise<{ + lookup: RawScenarioResult; + laterVerification: RawScenarioResult; +}> { + const lookupTimingsMs: number[] = []; + const verifyTimingsMs: number[] = []; + const lookupStatusCodes: number[] = []; + const verifyStatusCodes: number[] = []; + let lookupSuccess = 0; + let verifySuccess = 0; + + for (let index = 0; index < runs; index += 1) { + const verifyIssue = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: { ...cleanBundle, bundleId: `BENCH-LOOKUP-${index + 1}` } + }, + (payload) => JSON.parse(payload) as VerifyResponse + ); + + if (verifyIssue.statusCode !== 200 || !verifyIssue.body.receiptId) { + lookupStatusCodes.push(verifyIssue.statusCode); + verifyStatusCodes.push(verifyIssue.statusCode); + continue; + } + + const receiptId = verifyIssue.body.receiptId; + const lookup = await withMeasuredInject( + app, + { + method: 'GET', + url: `/api/v1/receipt/${receiptId}`, + headers: { 'x-api-key': DEFAULT_API_KEY } + }, + (payload) => JSON.parse(payload) as ReceiptDetailResponse + ); + lookupTimingsMs.push(lookup.elapsedMs); + lookupStatusCodes.push(lookup.statusCode); + if (lookup.statusCode === 200 && lookup.body.receipt?.receiptId === receiptId) { + lookupSuccess += 1; + } + + const laterVerification = await withMeasuredInject( + app, + { + method: 'POST', + url: `/api/v1/receipt/${receiptId}/verify`, + headers: { 'x-api-key': DEFAULT_API_KEY } + }, + (payload) => JSON.parse(payload) as StatusResponse + ); + verifyTimingsMs.push(laterVerification.elapsedMs); + verifyStatusCodes.push(laterVerification.statusCode); + if (laterVerification.statusCode === 200 && laterVerification.body.verified) { + verifySuccess += 1; + } + } + + return { + lookup: { + scenario: 'lookup', + purpose: 'Measure receipt retrieval latency through GET /api/v1/receipt/:receiptId.', + command: `${securityCommandBase} --scenario lookup --runs ${runs}`, + metricsCaptured: ['status lookup latency'], + expectedOutcome: 'HTTP 200 with persisted receipt payload.', + timingsMs: lookupTimingsMs, + statusCodes: lookupStatusCodes, + successCount: lookupSuccess, + failureCount: Math.max(0, runs - lookupSuccess), + reliabilityNotes: [`${lookupSuccess}/${runs} receipt lookup requests returned the stored receipt.`], + caveats: lookupSuccess === runs ? [] : ['Some lookup requests did not return the expected receipt payload.'] + }, + laterVerification: { + scenario: 'later-verification', + purpose: 'Measure later verification latency through POST /api/v1/receipt/:receiptId/verify.', + command: `${securityCommandBase} --scenario lookup --runs ${runs}`, + metricsCaptured: ['later verification latency'], + expectedOutcome: 'HTTP 200 with verified=true, integrityVerified=true, and signatureVerified=true.', + timingsMs: verifyTimingsMs, + statusCodes: verifyStatusCodes, + successCount: verifySuccess, + failureCount: Math.max(0, runs - verifySuccess), + reliabilityNotes: [`${verifySuccess}/${runs} later verification requests returned verified=true.`], + caveats: verifySuccess === runs ? [] : ['Some later verification requests did not return verified=true.'] + } + }; +} + +async function scenarioTampered( + app: FastifyInstance, + tamperedBundle: BundleInput, + securityCommandBase: string, + runs: number +): Promise { + const timingsMs: number[] = []; + const statusCodes: number[] = []; + const reliabilityNotes: string[] = []; + const caveats: string[] = []; + let mismatchDetected = 0; + + for (let index = 0; index < runs; index += 1) { + const verify = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: { ...tamperedBundle, bundleId: `BENCH-TAMPER-${index + 1}` } + }, + (payload) => JSON.parse(payload) as VerifyResponse + ); + + timingsMs.push(verify.elapsedMs); + statusCodes.push(verify.statusCode); + + const publicInputs = verify.body.zkpAttestation?.publicInputs; + if ( + verify.statusCode === 200 && + publicInputs?.declaredDocHash && + publicInputs.documentDigest && + publicInputs.declaredDocHash !== publicInputs.documentDigest + ) { + mismatchDetected += 1; + } + } + + reliabilityNotes.push(`${mismatchDetected}/${runs} tampered runs surfaced a declared hash vs observed digest mismatch.`); + if (mismatchDetected !== runs) { + caveats.push('Not every tampered run surfaced the expected digest mismatch signal.'); + } + + return { + scenario: 'tampered', + purpose: 'Measure latency for a tampered artifact submission where the declared hash does not match the supplied bytes.', + command: `${securityCommandBase} --scenario tampered --runs ${runs}`, + metricsCaptured: ['tampered artifact detection latency'], + expectedOutcome: 'HTTP 200 with mismatch visible in zkpAttestation.publicInputs declaredDocHash vs documentDigest.', + timingsMs, + statusCodes, + successCount: mismatchDetected, + failureCount: Math.max(0, runs - mismatchDetected), + reliabilityNotes, + caveats + }; +} + +async function scenarioRepeat( + app: FastifyInstance, + cleanBundle: BundleInput, + securityCommandBase: string, + runs: number +): Promise { + const timingsMs: number[] = []; + const statusCodes: number[] = []; + let successCount = 0; + + const repeatBundle = { ...cleanBundle, bundleId: 'BENCH-REPEAT-SAME' }; + + for (let index = 0; index < runs; index += 1) { + const response = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: repeatBundle + }, + (payload) => JSON.parse(payload) as VerifyResponse + ); + + timingsMs.push(response.elapsedMs); + statusCodes.push(response.statusCode); + if (response.statusCode === 200 && response.body.receiptId) { + successCount += 1; + } + } + + return { + scenario: 'repeat', + purpose: 'Measure stability when the same artifact payload is verified repeatedly.', + command: `${securityCommandBase} --scenario repeat --runs ${runs}`, + metricsCaptured: ['repeated-run stability'], + expectedOutcome: 'Repeated requests continue returning HTTP 200 and signed receipts without contract drift.', + timingsMs, + statusCodes, + successCount, + failureCount: Math.max(0, runs - successCount), + reliabilityNotes: [`${successCount}/${runs} repeated submissions of the same payload returned HTTP 200.`], + caveats: successCount === runs ? [] : ['Some repeated submissions failed or diverged from the expected response shape.'] + }; +} + +async function scenarioBadAuth( + app: FastifyInstance, + cleanBundle: BundleInput, + securityCommandBase: string +): Promise { + const timingsMs: number[] = []; + const statusCodes: number[] = []; + + const missingAuth = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + payload: cleanBundle + }, + (payload) => JSON.parse(payload) as { error?: string } + ); + timingsMs.push(missingAuth.elapsedMs); + statusCodes.push(missingAuth.statusCode); + + const invalidAuth = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': 'invalid-bench-api-key' }, + payload: cleanBundle + }, + (payload) => JSON.parse(payload) as { error?: string } + ); + timingsMs.push(invalidAuth.elapsedMs); + statusCodes.push(invalidAuth.statusCode); + + const successCount = statusCodes.filter((code) => code === 401 || code === 403).length; + + return { + scenario: 'bad-auth', + purpose: 'Confirm evaluator-visible fail-closed behavior for missing or invalid API authentication.', + command: `${securityCommandBase} --scenario bad-auth`, + metricsCaptured: ['auth failure response latency'], + expectedOutcome: 'Missing auth returns 401 and invalid auth returns 403.', + timingsMs, + statusCodes, + successCount, + failureCount: Math.max(0, 2 - successCount), + reliabilityNotes: [`${successCount}/2 auth-failure probes returned the expected 401 or 403 response.`], + caveats: successCount === 2 ? [] : ['One or more auth-failure probes did not return the expected status code.'] + }; +} + +async function scenarioMalformed( + app: FastifyInstance, + securityCommandBase: string +): Promise { + const timingsMs: number[] = []; + const statusCodes: number[] = []; + + const emptyPayload = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: {} + }, + (payload) => JSON.parse(payload) as { error?: string } + ); + timingsMs.push(emptyPayload.elapsedMs); + statusCodes.push(emptyPayload.statusCode); + + const malformedPayload = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: { bundleId: 'MALFORMED-001', doc: { docHash: 42 } } + }, + (payload) => JSON.parse(payload) as { error?: string } + ); + timingsMs.push(malformedPayload.elapsedMs); + statusCodes.push(malformedPayload.statusCode); + + const successCount = statusCodes.filter((code) => code === 400).length; + + return { + scenario: 'malformed', + purpose: 'Confirm malformed evaluator payloads fail early without entering the verification lifecycle.', + command: `${securityCommandBase} --scenario malformed`, + metricsCaptured: ['payload validation failure latency'], + expectedOutcome: 'HTTP 400 with Invalid payload error.', + timingsMs, + statusCodes, + successCount, + failureCount: Math.max(0, 2 - successCount), + reliabilityNotes: [`${successCount}/2 malformed payload probes returned HTTP 400.`], + caveats: successCount === 2 ? [] : ['One or more malformed payload probes did not return HTTP 400.'] + }; +} + +async function scenarioDependencyFailure( + app: FastifyInstance, + cleanBundle: BundleInput, + securityCommandBase: string +): Promise { + const subjectName = 'ACME HOLDINGS LLC'; + const response = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: { + ...cleanBundle, + bundleId: 'BENCH-DEPENDENCY-FAILURE', + registryScreening: { + subjectName, + sourceIds: ['sam_exclusions'], + forceRefresh: true + } + } + }, + (payload) => JSON.parse(payload) as VerifyResponse + ); + + const success = + response.statusCode === 200 && + response.body.decision !== 'ALLOW' && + Array.isArray(response.body.reasons) && + response.body.reasons.length > 0; + + return { + scenario: 'dependency-failure', + purpose: 'Measure fail-closed behavior when an external registry dependency is unavailable without configured access.', + command: `${securityCommandBase} --scenario dependency-failure`, + metricsCaptured: ['dependency failure response latency'], + expectedOutcome: 'HTTP 200 with a non-ALLOW decision reflecting compliance-gap or fail-closed handling.', + timingsMs: [response.elapsedMs], + statusCodes: [response.statusCode], + successCount: success ? 1 : 0, + failureCount: success ? 0 : 1, + reliabilityNotes: [ + success + ? 'Registry dependency failure produced a non-ALLOW decision without exposing internal dependency details.' + : 'Registry dependency failure did not produce the expected fail-closed decision.' + ], + caveats: success ? [] : ['Dependency-failure scenario did not reproduce the expected fail-closed outcome.'] + }; +} + +async function scenarioBatch( + app: FastifyInstance, + cleanBundle: BundleInput, + securityCommandBase: string, + batchSize: number +): Promise { + const timingsMs: number[] = []; + const statusCodes: number[] = []; + let successCount = 0; + + for (let index = 0; index < batchSize; index += 1) { + const response = await withMeasuredInject( + app, + { + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': DEFAULT_API_KEY }, + payload: { ...cleanBundle, bundleId: `BENCH-BATCH-${index + 1}` } + }, + (payload) => JSON.parse(payload) as VerifyResponse + ); + timingsMs.push(response.elapsedMs); + statusCodes.push(response.statusCode); + if (response.statusCode === 200 && response.body.receiptId) { + successCount += 1; + } + } + + return { + scenario: 'batch', + purpose: 'Measure sequential small-batch behavior over a short evaluator run.', + command: `${securityCommandBase} --scenario batch --batch-size ${batchSize}`, + metricsCaptured: ['small batch latency distribution'], + expectedOutcome: `All ${batchSize} sequential requests return HTTP 200 with signed receipts.`, + timingsMs, + statusCodes, + successCount, + failureCount: Math.max(0, batchSize - successCount), + reliabilityNotes: [`${successCount}/${batchSize} batch requests returned HTTP 200.`], + caveats: successCount === batchSize ? [] : ['The small batch run included one or more failed requests.'] + }; +} + +function toAggregatedResult(result: RawScenarioResult): AggregatedScenarioResult { + return { + ...result, + summary: summarizeTimings(result.timingsMs) + }; +} + +function pickScenarioList(requested: CliOptions['scenario']): ScenarioName[] { + if (requested === 'all') { + return ['clean', 'tampered', 'repeat', 'lookup', 'bad-auth', 'malformed', 'dependency-failure', 'batch']; + } + + return [requested]; +} + +function buildMarkdownReport(output: BenchmarkOutput): string { + const lines: string[] = []; + lines.push('# TrustSignal Benchmark Snapshot'); + lines.push(''); + lines.push('## Test Date/Time'); + lines.push(`- ${output.generatedAt}`); + lines.push(''); + lines.push('## Environment Description'); + lines.push(`- Node: ${output.environment.node}`); + lines.push(`- Platform: ${output.environment.platform} (${output.environment.arch})`); + lines.push(`- Host: ${output.environment.hostname}`); + lines.push(`- Temp database: ${output.environment.tempDatabase.engine} on 127.0.0.1:${output.environment.tempDatabase.port}`); + lines.push(`- Harness command: \`${output.command}\``); + lines.push(''); + lines.push('## Scenarios Executed'); + for (const scenario of output.scenarios) { + lines.push(`- ${scenario.scenario}: ${scenario.purpose}`); + } + lines.push(''); + lines.push('## Timing Summary Table'); + lines.push(''); + lines.push('| Scenario | Count | Min (ms) | Max (ms) | Mean (ms) | Median (ms) | p95 (ms) | Success / Total |'); + lines.push('| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |'); + for (const scenario of output.scenarios) { + lines.push( + `| ${scenario.scenario} | ${scenario.summary.count} | ${scenario.summary.minMs} | ${scenario.summary.maxMs} | ${scenario.summary.meanMs} | ${scenario.summary.medianMs} | ${scenario.summary.p95Ms} | ${scenario.successCount}/${scenario.successCount + scenario.failureCount} |` + ); + } + lines.push(''); + lines.push('## Reliability Notes'); + for (const scenario of output.scenarios) { + for (const note of scenario.reliabilityNotes) { + lines.push(`- ${scenario.scenario}: ${note}`); + } + } + lines.push(''); + lines.push('## Notable Failures Or Caveats'); + if (output.notableFailures.length === 0 && output.caveats.length === 0) { + lines.push('- No harness-level failures were observed in this run.'); + } else { + for (const failure of output.notableFailures) { + lines.push(`- ${failure}`); + } + for (const caveat of output.caveats) { + lines.push(`- ${caveat}`); + } + } + lines.push(''); + lines.push('## What This Means For Evaluators'); + lines.push('- This is a recent local evaluator run against the current public `/api/v1/*` lifecycle, not a production SLA.'); + lines.push('- The numbers are most useful for comparing request classes, verifying fail-closed behavior, and spotting regressions between local validation runs.'); + lines.push('- Clean verification, receipt lookup, and later verification can be exercised repeatedly with signed-receipt persistence under a reproducible local database setup.'); + lines.push('- Tampered and dependency-failure scenarios surface behavior signals that evaluators can test without exposing proof internals, signer infrastructure, or internal topology.'); + return `${lines.join('\n')}\n`; +} + +async function main() { + const options = parseArgs(process.argv.slice(2)); + const requestedScenarios = pickScenarioList(options.scenario); + const commandBase = 'npx tsx bench/run-bench.ts'; + const fullCommand = `${commandBase} --scenario ${options.scenario} --runs ${options.runs} --batch-size ${options.batchSize}`; + + await ensureBenchDirectories(options.outputDir); + + let tempPostgres: TempPostgres | null = null; + let app: FastifyInstance | null = null; + let prisma: PrismaClient | null = null; + + try { + tempPostgres = await startTemporaryPostgres(); + + process.env.DATABASE_URL = tempPostgres.databaseUrl; + process.env.API_KEYS = DEFAULT_API_KEY; + process.env.API_KEY_SCOPES = `${DEFAULT_API_KEY}=verify|read|anchor|revoke`; + delete process.env.SAM_API_KEY; + + const { buildServer } = await import('../apps/api/src/server.js'); + prisma = new PrismaClient(); + app = await buildServer(); + app.log.level = 'fatal'; + + const registry = await loadRegistry(); + await seedBaselineData(prisma, registry); + + const cleanArtifact = await loadFixtureText('clean-artifact.txt'); + const tamperedArtifact = await loadFixtureText('tampered-artifact.txt'); + + const cleanBundle = await buildBundle(registry, cleanArtifact, { + bundleId: 'BENCH-CLEAN-SEED', + parcelId: 'BENCH-PARCEL-001', + includePdfBase64: false + }); + const tamperedBundle = await buildBundle(registry, tamperedArtifact, { + bundleId: 'BENCH-TAMPER-SEED', + parcelId: 'BENCH-PARCEL-002', + declaredDocHash: cleanBundle.doc.docHash, + includePdfBase64: true + }); + + const scenarioResults: AggregatedScenarioResult[] = []; + let verificationRequestLatency: TimingSummary | null = null; + let signedReceiptGenerationLatency: TimingSummary | null = null; + let laterVerificationLatency: TimingSummary | null = null; + let statusLookupLatency: TimingSummary | null = null; + let tamperedArtifactDetectionLatency: TimingSummary | null = null; + let repeatedRunStability: TimingSummary | null = null; + + if (requestedScenarios.includes('clean')) { + const clean = await scenarioClean(app, cleanBundle, commandBase, options.runs); + const aggregated = toAggregatedResult(clean.scenario); + scenarioResults.push(aggregated); + verificationRequestLatency = aggregated.summary; + signedReceiptGenerationLatency = summarizeTimings(clean.signingTimings); + } + + if (requestedScenarios.includes('tampered')) { + const tampered = toAggregatedResult( + await scenarioTampered(app, tamperedBundle, commandBase, options.runs) + ); + scenarioResults.push(tampered); + tamperedArtifactDetectionLatency = tampered.summary; + } + + if (requestedScenarios.includes('repeat')) { + const repeated = toAggregatedResult( + await scenarioRepeat(app, cleanBundle, commandBase, options.runs) + ); + scenarioResults.push(repeated); + repeatedRunStability = repeated.summary; + } + + if (requestedScenarios.includes('lookup')) { + const lookup = await scenarioLookup(app, cleanBundle, commandBase, options.runs); + const lookupAggregated = toAggregatedResult(lookup.lookup); + const laterVerificationAggregated = toAggregatedResult(lookup.laterVerification); + scenarioResults.push(lookupAggregated, laterVerificationAggregated); + statusLookupLatency = lookupAggregated.summary; + laterVerificationLatency = laterVerificationAggregated.summary; + } + + if (requestedScenarios.includes('bad-auth')) { + scenarioResults.push( + toAggregatedResult(await scenarioBadAuth(app, cleanBundle, commandBase)) + ); + } + + if (requestedScenarios.includes('malformed')) { + scenarioResults.push( + toAggregatedResult(await scenarioMalformed(app, commandBase)) + ); + } + + if (requestedScenarios.includes('dependency-failure')) { + scenarioResults.push( + toAggregatedResult(await scenarioDependencyFailure(app, cleanBundle, commandBase)) + ); + } + + if (requestedScenarios.includes('batch')) { + scenarioResults.push( + toAggregatedResult(await scenarioBatch(app, cleanBundle, commandBase, options.batchSize)) + ); + } + + const notableFailures = scenarioResults + .filter((scenario) => scenario.failureCount > 0) + .map((scenario) => `${scenario.scenario}: ${scenario.failureCount} failed observation(s) out of ${scenario.successCount + scenario.failureCount}.`); + const caveats = scenarioResults.flatMap((scenario) => scenario.caveats.map((note) => `${scenario.scenario}: ${note}`)); + if (requestedScenarios.includes('tampered')) { + caveats.push( + 'tampered: The tampered scenario uses a local byte fixture to force a declared-hash mismatch. It is suitable for evaluator behavior checks, not for asserting document-parser completeness.' + ); + } + + const output: BenchmarkOutput = { + generatedAt: new Date().toISOString(), + command: fullCommand, + environment: { + node: process.version, + platform: os.platform(), + arch: os.arch(), + hostname: os.hostname(), + tempDatabase: { + engine: 'postgresql', + port: tempPostgres.port, + dbName: tempPostgres.dbName + } + }, + harness: { + scenario: options.scenario, + runs: options.runs, + batchSize: options.batchSize + }, + metrics: { + verificationRequestLatency, + signedReceiptGenerationLatency, + laterVerificationLatency, + statusLookupLatency, + tamperedArtifactDetectionLatency, + repeatedRunStability + }, + scenarios: scenarioResults, + notableFailures, + caveats + }; + + const jsonPath = path.join(options.outputDir, 'latest.json'); + const markdownPath = path.join(options.outputDir, 'latest.md'); + await fs.writeFile(jsonPath, `${JSON.stringify(output, null, 2)}\n`, 'utf8'); + await fs.writeFile(markdownPath, buildMarkdownReport(output), 'utf8'); + + console.log(JSON.stringify({ jsonPath, markdownPath }, null, 2)); + } finally { + if (app) { + await app.close(); + } + if (prisma) { + await prisma.$disconnect(); + } + await stopTemporaryPostgres(tempPostgres); + } +} + +main().catch((error) => { + console.error(error instanceof Error ? error.stack || error.message : String(error)); + process.exitCode = 1; +}); diff --git a/bench/scenarios.md b/bench/scenarios.md new file mode 100644 index 0000000..131340a --- /dev/null +++ b/bench/scenarios.md @@ -0,0 +1,57 @@ +# TrustSignal Scenario Matrix + +## Clean Artifact Verification + +- Purpose: Measure baseline evaluator latency for a clean artifact flowing through `POST /api/v1/verify`. +- Command or script path: `npx tsx bench/run-bench.ts --scenario clean --runs 15` +- Expected outcome: HTTP `200` with `receiptId`, `receiptHash`, and `receiptSignature`. +- Metric(s) captured: verification request latency, signed receipt generation latency. + +## Tampered Artifact Verification + +- Purpose: Measure how quickly the evaluator flow records a declared-hash vs observed-digest mismatch for tampered bytes. +- Command or script path: `npx tsx bench/run-bench.ts --scenario tampered --runs 15` +- Expected outcome: HTTP `200` with mismatch visible in `zkpAttestation.publicInputs.declaredDocHash` vs `documentDigest`. +- Metric(s) captured: tampered artifact detection latency. + +## Repeated Verification Of Same Artifact + +- Purpose: Measure stability when the same payload is submitted repeatedly through the public verification path. +- Command or script path: `npx tsx bench/run-bench.ts --scenario repeat --runs 15` +- Expected outcome: repeated HTTP `200` responses with signed receipts and no contract drift. +- Metric(s) captured: repeated-run stability, per-run latency spread. + +## Receipt Retrieval / Status Check + +- Purpose: Measure persisted receipt lookup and later verification latency after successful issuance. +- Command or script path: `npx tsx bench/run-bench.ts --scenario lookup --runs 15` +- Expected outcome: `GET /api/v1/receipt/:receiptId` returns HTTP `200`; `POST /api/v1/receipt/:receiptId/verify` returns HTTP `200` with `verified=true`. +- Metric(s) captured: status lookup latency, later verification latency. + +## Bad Auth Or Missing Auth + +- Purpose: Confirm evaluator-visible fail-closed behavior for missing or invalid API authentication. +- Command or script path: `npx tsx bench/run-bench.ts --scenario bad-auth` +- Expected outcome: missing auth returns HTTP `401`; invalid auth returns HTTP `403`. +- Metric(s) captured: auth failure response latency. + +## Missing Or Malformed Payload + +- Purpose: Confirm invalid evaluator payloads fail at the API boundary instead of entering the verification lifecycle. +- Command or script path: `npx tsx bench/run-bench.ts --scenario malformed` +- Expected outcome: HTTP `400` with invalid payload errors. +- Metric(s) captured: payload validation failure latency. + +## Dependency Failure / Fail-Closed Behavior + +- Purpose: Reproduce a safe dependency-failure path using registry screening without configured external access and verify the response does not silently pass as clean. +- Command or script path: `npx tsx bench/run-bench.ts --scenario dependency-failure` +- Expected outcome: HTTP `200` with a non-`ALLOW` decision that reflects compliance-gap or fail-closed handling. +- Metric(s) captured: dependency failure response latency. + +## Small Batch Run + +- Purpose: Measure short sequential batch behavior for evaluator-style repeated requests. +- Command or script path: `npx tsx bench/run-bench.ts --scenario batch --batch-size 10` +- Expected outcome: all sequential requests return HTTP `200` with signed receipts. +- Metric(s) captured: small batch latency distribution, success rate across the run. diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index d83bc15..43d059b 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -48,6 +48,19 @@ The public evaluation path in this repository is the `/api/v1/*` surface: Canonical contract and payload examples live in [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) and the [`examples/`](../../examples) directory. +## Current Evaluator Metrics + +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: + +- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` +- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` +- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` +- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` +- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` +- repeated-run stability for the same artifact payload: mean `3.24 ms`, median `3.21 ms`, p95 `4.04 ms` + +This snapshot comes from a recent local evaluator run. It is useful for comparing request classes and checking regressions, not for inferring guaranteed deployment latency. + ## Integration Fit TrustSignal fits behind an existing workflow such as: diff --git a/docs/partner-eval/try-the-api.md b/docs/partner-eval/try-the-api.md index d6de701..11f6252 100644 --- a/docs/partner-eval/try-the-api.md +++ b/docs/partner-eval/try-the-api.md @@ -67,6 +67,18 @@ curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/anchor/$RECEIPT_ID" \ Revocation is part of the public contract, but it requires issuer authorization headers in addition to the API key. Use the Postman collection for the full request template. +## Recent Verification Timing + +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: + +- `POST /api/v1/verify` clean-path latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` +- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` +- `GET /api/v1/receipt/:receiptId` lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` +- `POST /api/v1/receipt/:receiptId/verify` later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` +- tampered artifact detection path: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` + +These numbers come from a recent local benchmark harness run against the current evaluator path. They are current validation data, not guaranteed service latency. + ## Production Readiness - Authentication: use `x-api-key` with the scopes required for verify, read, anchor, or revoke operations. diff --git a/docs/verification-lifecycle.md b/docs/verification-lifecycle.md index be3568e..f6cbf96 100644 --- a/docs/verification-lifecycle.md +++ b/docs/verification-lifecycle.md @@ -78,3 +78,15 @@ flowchart TD - The TrustSignal API Gateway is the public integration boundary for verification and later verification requests. - The private verification engine remains non-public. - The public outputs are verification signals, signed verification receipts, and verifiable provenance suitable for later verification. + +## Current Evaluator Metrics + +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: + +- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` +- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` +- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` +- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` +- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` + +This benchmark snapshot is from a recent local evaluator run using the current public lifecycle. It helps characterize the flow in this repository without making production-performance claims. diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md index 1283e73..91d58ef 100644 --- a/wiki/Quick-Verification-Example.md +++ b/wiki/Quick-Verification-Example.md @@ -119,6 +119,18 @@ curl -X POST -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ https://api.trustsignal.example/api/v1/receipt/2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a/verify ``` +### Recent Verification Timing + +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: + +- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` +- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` +- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` +- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` +- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` + +This is a recent local evaluator benchmark snapshot, not a production guarantee. The tampered path is most useful as a behavior check for mismatch handling rather than a parser-completeness claim. + ### What This Does Not Expose This public example does not expose: From e068cad450caadd338aa3551548f0e04406c0c28 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 18:19:09 -0500 Subject: [PATCH 09/25] docs: add evaluator benchmark summary and benchmark metadata --- README.md | 16 +- bench/results/latest.json | 314 +++++++++++++------------ bench/results/latest.md | 32 ++- bench/run-bench.ts | 26 +- docs/partner-eval/benchmark-summary.md | 82 +++++++ docs/partner-eval/overview.md | 15 +- docs/partner-eval/try-the-api.md | 12 +- 7 files changed, 311 insertions(+), 186 deletions(-) create mode 100644 docs/partner-eval/benchmark-summary.md diff --git a/README.md b/README.md index 327a1c9..3aef3ee 100644 --- a/README.md +++ b/README.md @@ -239,14 +239,14 @@ npm run build ## Current Evaluator Metrics -Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: - -- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` -- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` -- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` -- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` -- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` -- repeated-run stability for the same artifact payload: mean `3.24 ms`, median `3.21 ms`, p95 `4.04 ms` +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`. A partner-facing interpretation is available in [docs/partner-eval/benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md). + +- clean verification request latency: mean `5.24 ms`, median `4.11 ms`, p95 `21.65 ms` +- signed receipt generation latency: mean `0.34 ms`, median `0.32 ms`, p95 `0.63 ms` +- receipt lookup latency: mean `0.57 ms`, median `0.56 ms`, p95 `0.63 ms` +- later verification latency: mean `0.77 ms`, median `0.71 ms`, p95 `1.08 ms` +- tampered artifact detection latency: mean `7.76 ms`, median `5.13 ms`, p95 `42.82 ms` +- repeated-run stability for the same artifact payload: mean `3.24 ms`, median `3.16 ms`, p95 `3.69 ms` This is a recent local evaluator run against the current `/api/v1/*` lifecycle with a temporary local PostgreSQL instance. It is a benchmark snapshot for evaluation and regression tracking, not a production guarantee or SLA. diff --git a/bench/results/latest.json b/bench/results/latest.json index c0307e2..5aa7309 100644 --- a/bench/results/latest.json +++ b/bench/results/latest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-03-12T22:22:06.846Z", + "generatedAt": "2026-03-12T22:30:04.260Z", "command": "npx tsx bench/run-bench.ts --scenario all --runs 15 --batch-size 10", "environment": { "node": "v22.14.0", @@ -8,63 +8,73 @@ "hostname": "Christophers-Mac-mini.local", "tempDatabase": { "engine": "postgresql", - "port": 63688, + "port": 64030, "dbName": "trustsignal_bench" - } + }, + "notes": [ + "Local benchmark run on a developer workstation using a temporary PostgreSQL instance.", + "The harness exercises the public /api/v1/* evaluator lifecycle through Fastify injection rather than an external network hop.", + "No production load balancer, cross-service network latency, or remote datastore variance is included in these numbers." + ] }, "harness": { "scenario": "all", "runs": 15, - "batchSize": 10 + "batchSize": 10, + "sampleNotes": [ + "Primary timing samples use 15 iterations per scenario when applicable.", + "The sequential batch scenario uses 10 requests.", + "First-run initialization effects may appear in max and p95 values, especially on scenarios that touch additional parsing or compliance paths." + ] }, "metrics": { "verificationRequestLatency": { "count": 15, - "minMs": 3.19, - "maxMs": 19.57, - "meanMs": 5.06, - "medianMs": 3.78, - "p95Ms": 19.57 + "minMs": 3.21, + "maxMs": 21.65, + "meanMs": 5.24, + "medianMs": 4.11, + "p95Ms": 21.65 }, "signedReceiptGenerationLatency": { "count": 15, - "minMs": 0.28, - "maxMs": 0.9, - "meanMs": 0.38, + "minMs": 0.27, + "maxMs": 0.63, + "meanMs": 0.34, "medianMs": 0.32, - "p95Ms": 0.9 + "p95Ms": 0.63 }, "laterVerificationLatency": { "count": 15, - "minMs": 0.7, - "maxMs": 1.07, - "meanMs": 0.76, - "medianMs": 0.72, - "p95Ms": 1.07 + "minMs": 0.67, + "maxMs": 1.08, + "meanMs": 0.77, + "medianMs": 0.71, + "p95Ms": 1.08 }, "statusLookupLatency": { "count": 15, - "minMs": 0.56, - "maxMs": 0.71, - "meanMs": 0.6, - "medianMs": 0.57, - "p95Ms": 0.71 + "minMs": 0.51, + "maxMs": 0.63, + "meanMs": 0.57, + "medianMs": 0.56, + "p95Ms": 0.63 }, "tamperedArtifactDetectionLatency": { "count": 15, - "minMs": 4.69, - "maxMs": 42.84, - "meanMs": 8.02, - "medianMs": 5.01, - "p95Ms": 42.84 + "minMs": 4.74, + "maxMs": 42.82, + "meanMs": 7.76, + "medianMs": 5.13, + "p95Ms": 42.82 }, "repeatedRunStability": { "count": 15, - "minMs": 3, - "maxMs": 4.04, + "minMs": 3.03, + "maxMs": 3.69, "meanMs": 3.24, - "medianMs": 3.21, - "p95Ms": 4.04 + "medianMs": 3.16, + "p95Ms": 3.69 } }, "scenarios": [ @@ -78,21 +88,21 @@ ], "expectedOutcome": "HTTP 200 with receiptId, receiptHash, and receiptSignature present.", "timingsMs": [ - 19.57, - 5.58, - 4.46, - 4.2, - 5.58, - 3.9, - 3.55, + 21.65, + 4.85, + 4.37, + 4.24, + 5.91, + 4.11, + 4.42, + 3.5, + 4.16, + 3.85, + 3.8, 3.51, - 4.4, - 3.78, 3.74, - 3.48, - 3.37, - 3.19, - 3.53 + 3.22, + 3.21 ], "statusCodes": [ 200, @@ -119,11 +129,11 @@ "caveats": [], "summary": { "count": 15, - "minMs": 3.19, - "maxMs": 19.57, - "meanMs": 5.06, - "medianMs": 3.78, - "p95Ms": 19.57 + "minMs": 3.21, + "maxMs": 21.65, + "meanMs": 5.24, + "medianMs": 4.11, + "p95Ms": 21.65 } }, { @@ -135,21 +145,21 @@ ], "expectedOutcome": "HTTP 200 with mismatch visible in zkpAttestation.publicInputs declaredDocHash vs documentDigest.", "timingsMs": [ - 42.84, - 11.13, - 6.03, - 4.93, - 5.01, - 4.84, - 5.01, - 4.99, - 5.61, - 5.52, - 4.85, - 4.69, - 4.72, - 5.19, - 5.01 + 42.82, + 5.77, + 5.24, + 5.03, + 5.13, + 5.04, + 6.43, + 5.18, + 5.63, + 5.13, + 4.87, + 4.74, + 4.76, + 4.82, + 5.79 ], "statusCodes": [ 200, @@ -176,11 +186,11 @@ "caveats": [], "summary": { "count": 15, - "minMs": 4.69, - "maxMs": 42.84, - "meanMs": 8.02, - "medianMs": 5.01, - "p95Ms": 42.84 + "minMs": 4.74, + "maxMs": 42.82, + "meanMs": 7.76, + "medianMs": 5.13, + "p95Ms": 42.82 } }, { @@ -192,21 +202,21 @@ ], "expectedOutcome": "Repeated requests continue returning HTTP 200 and signed receipts without contract drift.", "timingsMs": [ - 3.48, - 3.21, - 4.04, - 3.42, - 3.25, - 3.24, - 3.23, - 3.09, - 3.05, - 3.09, - 3.37, - 3.11, - 3.04, - 3, - 3.03 + 3.36, + 3.16, + 3.33, + 3.35, + 3.34, + 3.1, + 3.69, + 3.16, + 3.1, + 3.19, + 3.1, + 3.1, + 3.07, + 3.03, + 3.52 ], "statusCodes": [ 200, @@ -233,11 +243,11 @@ "caveats": [], "summary": { "count": 15, - "minMs": 3, - "maxMs": 4.04, + "minMs": 3.03, + "maxMs": 3.69, "meanMs": 3.24, - "medianMs": 3.21, - "p95Ms": 4.04 + "medianMs": 3.16, + "p95Ms": 3.69 } }, { @@ -249,21 +259,21 @@ ], "expectedOutcome": "HTTP 200 with persisted receipt payload.", "timingsMs": [ - 0.64, - 0.71, - 0.62, - 0.59, - 0.58, 0.57, 0.56, - 0.56, - 0.71, - 0.57, - 0.56, - 0.56, + 0.6, 0.57, - 0.57, - 0.56 + 0.55, + 0.51, + 0.62, + 0.54, + 0.63, + 0.55, + 0.55, + 0.54, + 0.58, + 0.6, + 0.54 ], "statusCodes": [ 200, @@ -290,11 +300,11 @@ "caveats": [], "summary": { "count": 15, - "minMs": 0.56, - "maxMs": 0.71, - "meanMs": 0.6, - "medianMs": 0.57, - "p95Ms": 0.71 + "minMs": 0.51, + "maxMs": 0.63, + "meanMs": 0.57, + "medianMs": 0.56, + "p95Ms": 0.63 } }, { @@ -306,21 +316,21 @@ ], "expectedOutcome": "HTTP 200 with verified=true, integrityVerified=true, and signatureVerified=true.", "timingsMs": [ - 1.07, - 0.78, - 0.76, - 0.74, - 0.71, - 0.71, + 1.08, 0.71, + 0.74, 0.71, - 0.86, + 0.7, + 0.67, + 0.78, + 0.68, + 0.89, 0.72, - 0.79, + 0.78, + 0.69, + 1.07, 0.71, - 0.75, - 0.7, - 0.71 + 0.67 ], "statusCodes": [ 200, @@ -347,11 +357,11 @@ "caveats": [], "summary": { "count": 15, - "minMs": 0.7, - "maxMs": 1.07, - "meanMs": 0.76, - "medianMs": 0.72, - "p95Ms": 1.07 + "minMs": 0.67, + "maxMs": 1.08, + "meanMs": 0.77, + "medianMs": 0.71, + "p95Ms": 1.08 } }, { @@ -363,7 +373,7 @@ ], "expectedOutcome": "Missing auth returns 401 and invalid auth returns 403.", "timingsMs": [ - 0.23, + 0.24, 0.15 ], "statusCodes": [ @@ -379,10 +389,10 @@ "summary": { "count": 2, "minMs": 0.15, - "maxMs": 0.23, - "meanMs": 0.19, - "medianMs": 0.19, - "p95Ms": 0.23 + "maxMs": 0.24, + "meanMs": 0.2, + "medianMs": 0.2, + "p95Ms": 0.24 } }, { @@ -394,8 +404,8 @@ ], "expectedOutcome": "HTTP 400 with Invalid payload error.", "timingsMs": [ - 0.46, - 0.39 + 0.48, + 0.37 ], "statusCodes": [ 400, @@ -409,11 +419,11 @@ "caveats": [], "summary": { "count": 2, - "minMs": 0.39, - "maxMs": 0.46, - "meanMs": 0.43, - "medianMs": 0.43, - "p95Ms": 0.46 + "minMs": 0.37, + "maxMs": 0.48, + "meanMs": 0.42, + "medianMs": 0.42, + "p95Ms": 0.48 } }, { @@ -425,7 +435,7 @@ ], "expectedOutcome": "HTTP 200 with a non-ALLOW decision reflecting compliance-gap or fail-closed handling.", "timingsMs": [ - 13.35 + 13.28 ], "statusCodes": [ 200 @@ -438,11 +448,11 @@ "caveats": [], "summary": { "count": 1, - "minMs": 13.35, - "maxMs": 13.35, - "meanMs": 13.35, - "medianMs": 13.35, - "p95Ms": 13.35 + "minMs": 13.28, + "maxMs": 13.28, + "meanMs": 13.28, + "medianMs": 13.28, + "p95Ms": 13.28 } }, { @@ -455,15 +465,15 @@ "expectedOutcome": "All 10 sequential requests return HTTP 200 with signed receipts.", "timingsMs": [ 3.31, + 3.14, + 3.13, + 3.79, + 3.51, + 3.25, 3.09, - 3.07, - 3.26, - 3.37, - 3.35, + 3.13, 3.11, - 3.57, - 3.18, - 3.09 + 3.16 ], "statusCodes": [ 200, @@ -485,11 +495,11 @@ "caveats": [], "summary": { "count": 10, - "minMs": 3.07, - "maxMs": 3.57, - "meanMs": 3.24, - "medianMs": 3.22, - "p95Ms": 3.57 + "minMs": 3.09, + "maxMs": 3.79, + "meanMs": 3.26, + "medianMs": 3.15, + "p95Ms": 3.79 } } ], diff --git a/bench/results/latest.md b/bench/results/latest.md index ad34128..6e217a1 100644 --- a/bench/results/latest.md +++ b/bench/results/latest.md @@ -1,15 +1,25 @@ # TrustSignal Benchmark Snapshot ## Test Date/Time -- 2026-03-12T22:22:06.846Z +- 2026-03-12T22:30:04.260Z ## Environment Description - Node: v22.14.0 - Platform: darwin (arm64) - Host: Christophers-Mac-mini.local -- Temp database: postgresql on 127.0.0.1:63688 +- Temp database: postgresql on 127.0.0.1:64030 - Harness command: `npx tsx bench/run-bench.ts --scenario all --runs 15 --batch-size 10` +## Iteration / Sample Notes +- Primary timing samples use 15 iterations per scenario when applicable. +- The sequential batch scenario uses 10 requests. +- First-run initialization effects may appear in max and p95 values, especially on scenarios that touch additional parsing or compliance paths. + +## Environment Notes +- Local benchmark run on a developer workstation using a temporary PostgreSQL instance. +- The harness exercises the public /api/v1/* evaluator lifecycle through Fastify injection rather than an external network hop. +- No production load balancer, cross-service network latency, or remote datastore variance is included in these numbers. + ## Scenarios Executed - clean: Measure end-to-end clean artifact verification through POST /api/v1/verify. - tampered: Measure latency for a tampered artifact submission where the declared hash does not match the supplied bytes. @@ -25,15 +35,15 @@ | Scenario | Count | Min (ms) | Max (ms) | Mean (ms) | Median (ms) | p95 (ms) | Success / Total | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| clean | 15 | 3.19 | 19.57 | 5.06 | 3.78 | 19.57 | 15/15 | -| tampered | 15 | 4.69 | 42.84 | 8.02 | 5.01 | 42.84 | 15/15 | -| repeat | 15 | 3 | 4.04 | 3.24 | 3.21 | 4.04 | 15/15 | -| lookup | 15 | 0.56 | 0.71 | 0.6 | 0.57 | 0.71 | 15/15 | -| later-verification | 15 | 0.7 | 1.07 | 0.76 | 0.72 | 1.07 | 15/15 | -| bad-auth | 2 | 0.15 | 0.23 | 0.19 | 0.19 | 0.23 | 2/2 | -| malformed | 2 | 0.39 | 0.46 | 0.43 | 0.43 | 0.46 | 2/2 | -| dependency-failure | 1 | 13.35 | 13.35 | 13.35 | 13.35 | 13.35 | 1/1 | -| batch | 10 | 3.07 | 3.57 | 3.24 | 3.22 | 3.57 | 10/10 | +| clean | 15 | 3.21 | 21.65 | 5.24 | 4.11 | 21.65 | 15/15 | +| tampered | 15 | 4.74 | 42.82 | 7.76 | 5.13 | 42.82 | 15/15 | +| repeat | 15 | 3.03 | 3.69 | 3.24 | 3.16 | 3.69 | 15/15 | +| lookup | 15 | 0.51 | 0.63 | 0.57 | 0.56 | 0.63 | 15/15 | +| later-verification | 15 | 0.67 | 1.08 | 0.77 | 0.71 | 1.08 | 15/15 | +| bad-auth | 2 | 0.15 | 0.24 | 0.2 | 0.2 | 0.24 | 2/2 | +| malformed | 2 | 0.37 | 0.48 | 0.42 | 0.42 | 0.48 | 2/2 | +| dependency-failure | 1 | 13.28 | 13.28 | 13.28 | 13.28 | 13.28 | 1/1 | +| batch | 10 | 3.09 | 3.79 | 3.26 | 3.15 | 3.79 | 10/10 | ## Reliability Notes - clean: 15/15 clean verification requests returned signed receipts. diff --git a/bench/run-bench.ts b/bench/run-bench.ts index 18a8e17..bfc75ae 100644 --- a/bench/run-bench.ts +++ b/bench/run-bench.ts @@ -96,11 +96,13 @@ type BenchmarkOutput = { port: number; dbName: string; }; + notes: string[]; }; harness: { scenario: string; runs: number; batchSize: number; + sampleNotes: string[]; }; metrics: { verificationRequestLatency: TimingSummary | null; @@ -879,6 +881,16 @@ function buildMarkdownReport(output: BenchmarkOutput): string { lines.push(`- Temp database: ${output.environment.tempDatabase.engine} on 127.0.0.1:${output.environment.tempDatabase.port}`); lines.push(`- Harness command: \`${output.command}\``); lines.push(''); + lines.push('## Iteration / Sample Notes'); + for (const note of output.harness.sampleNotes) { + lines.push(`- ${note}`); + } + lines.push(''); + lines.push('## Environment Notes'); + for (const note of output.environment.notes) { + lines.push(`- ${note}`); + } + lines.push(''); lines.push('## Scenarios Executed'); for (const scenario of output.scenarios) { lines.push(`- ${scenario.scenario}: ${scenario.purpose}`); @@ -1051,12 +1063,22 @@ async function main() { engine: 'postgresql', port: tempPostgres.port, dbName: tempPostgres.dbName - } + }, + notes: [ + 'Local benchmark run on a developer workstation using a temporary PostgreSQL instance.', + 'The harness exercises the public /api/v1/* evaluator lifecycle through Fastify injection rather than an external network hop.', + 'No production load balancer, cross-service network latency, or remote datastore variance is included in these numbers.' + ] }, harness: { scenario: options.scenario, runs: options.runs, - batchSize: options.batchSize + batchSize: options.batchSize, + sampleNotes: [ + `Primary timing samples use ${options.runs} iterations per scenario when applicable.`, + `The sequential batch scenario uses ${options.batchSize} requests.`, + 'First-run initialization effects may appear in max and p95 values, especially on scenarios that touch additional parsing or compliance paths.' + ] }, metrics: { verificationRequestLatency, diff --git a/docs/partner-eval/benchmark-summary.md b/docs/partner-eval/benchmark-summary.md new file mode 100644 index 0000000..eea8664 --- /dev/null +++ b/docs/partner-eval/benchmark-summary.md @@ -0,0 +1,82 @@ +# TrustSignal Evaluator Benchmark Summary + +This page summarizes the most recent local evaluator benchmark snapshot from [bench/results/latest.json](/Users/christopher/Projects/trustsignal/bench/results/latest.json) and [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md). + +## Executive Summary + +The current local evaluator run shows that the public `/api/v1/*` lifecycle is fast and stable in a reproducible local setup. Clean verification, receipt lookup, later verification, and repeated submissions all completed successfully across the sampled runs, with clean verification averaging `5.24 ms`, receipt lookup `0.57 ms`, and later verification `0.77 ms`. + +The tampered artifact path also completed successfully across all sampled runs, with a median of `5.13 ms`. Its `42.82 ms` p95 is materially higher than the median and should be treated as a follow-up item rather than a normal steady-state expectation. The current evidence suggests local first-run or parser-path variance, not a correctness failure, but the spread is large enough to call out explicitly. + +## Metric Table + +| Metric | Samples | Min (ms) | Max (ms) | Mean (ms) | Median (ms) | p95 (ms) | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| Verification request latency | 15 | 3.21 | 21.65 | 5.24 | 4.11 | 21.65 | +| Signed receipt generation latency | 15 | 0.27 | 0.63 | 0.34 | 0.32 | 0.63 | +| Receipt lookup latency | 15 | 0.51 | 0.63 | 0.57 | 0.56 | 0.63 | +| Later verification latency | 15 | 0.67 | 1.08 | 0.77 | 0.71 | 1.08 | +| Tampered artifact detection latency | 15 | 4.74 | 42.82 | 7.76 | 5.13 | 42.82 | +| Repeated-run stability latency | 15 | 3.03 | 3.69 | 3.24 | 3.16 | 3.69 | + +## Scenario Coverage + +- `clean`: end-to-end `POST /api/v1/verify` with signed receipt issuance +- `tampered`: declared-hash vs observed-digest mismatch path +- `repeat`: repeated verification of the same artifact payload +- `lookup`: `GET /api/v1/receipt/:receiptId` +- `later-verification`: `POST /api/v1/receipt/:receiptId/verify` +- `bad-auth`: missing or invalid `x-api-key` +- `malformed`: missing or malformed request payload +- `dependency-failure`: safe fail-closed registry-screening path +- `batch`: short sequential batch run + +Reliability notes from the latest run: + +- `15/15` clean verification requests returned signed receipts. +- `15/15` tampered runs surfaced a declared-hash vs observed-digest mismatch. +- `15/15` later verification requests returned `verified=true`. +- `10/10` sequential batch requests returned `HTTP 200`. + +## Environment And Caveats + +- Benchmark timestamp: `2026-03-12T22:30:04.260Z` +- Runtime: Node `v22.14.0` +- Platform: `darwin (arm64)` +- Database: temporary local PostgreSQL instance on `127.0.0.1:64030` +- Primary sample size: `15` iterations per applicable scenario +- Sequential batch sample size: `10` +- Harness command: `npx tsx bench/run-bench.ts --scenario all --runs 15 --batch-size 10` + +Current caveats: + +- This is a local developer-workstation benchmark, not a hosted environment benchmark. +- The harness uses Fastify injection to exercise the public evaluator routes without adding external network-hop latency. +- The tampered scenario uses a local byte fixture to force a declared-hash mismatch. It is useful for evaluator behavior checks, not for claiming document-parser completeness. + +## How To Interpret These Numbers + +Treat these values as recent local evaluator benchmark results. They are useful for comparing request classes, spotting regressions, and demonstrating lifecycle behavior, but they are not production SLA claims and should not be read as guaranteed deployment latency. + +The medians are the best quick read for typical local behavior. The p95 values are more useful for spotting variance and warm-up effects. In this run, the tampered-path p95 spike is large enough to watch in future snapshots. + +## What The Benchmark Does Measure + +- Public evaluator lifecycle timing for the current `/api/v1/*` verification path +- Signed receipt issuance timing using the same receipt-building and signing primitives used by the evaluator flow +- Receipt retrieval and later verification timing +- Repeatability across multiple local runs +- API-boundary failure behavior for bad auth and malformed payloads +- A safe local fail-closed dependency scenario + +## What The Benchmark Does Not Measure + +- Production network latency, cold starts behind hosting infrastructure, or edge routing effects +- Multi-tenant concurrency, sustained throughput, or horizontal scaling behavior +- Remote database latency, failover behavior, or managed-service variance +- Full end-user browser timing +- Proof internals, signer infrastructure specifics, internal topology, or any non-public runtime surfaces + +## Tampered Path Variance Review + +The tampered artifact path recorded a median of `5.13 ms` but a p95 of `42.82 ms`. Given the local fixture-driven setup and the parser/compliance code touched by that path, this looks more like local path variance than an indication that tamper detection is unreliable. Even so, the spread is large enough that it should be treated as a follow-up item in future benchmark runs rather than dismissed as unimportant expected variance. diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 43d059b..1497427 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -31,6 +31,7 @@ Start with these evaluator assets: - [Evaluator quickstart](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) - [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [Benchmark summary](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) - [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) - [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) @@ -50,14 +51,14 @@ Canonical contract and payload examples live in [openapi.yaml](/Users/christophe ## Current Evaluator Metrics -Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`. For evaluator-facing interpretation and caveats, see [benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md). -- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` -- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` -- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` -- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` -- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` -- repeated-run stability for the same artifact payload: mean `3.24 ms`, median `3.21 ms`, p95 `4.04 ms` +- clean verification request latency: mean `5.24 ms`, median `4.11 ms`, p95 `21.65 ms` +- signed receipt generation latency: mean `0.34 ms`, median `0.32 ms`, p95 `0.63 ms` +- receipt lookup latency: mean `0.57 ms`, median `0.56 ms`, p95 `0.63 ms` +- later verification latency: mean `0.77 ms`, median `0.71 ms`, p95 `1.08 ms` +- tampered artifact detection latency: mean `7.76 ms`, median `5.13 ms`, p95 `42.82 ms` +- repeated-run stability for the same artifact payload: mean `3.24 ms`, median `3.16 ms`, p95 `3.69 ms` This snapshot comes from a recent local evaluator run. It is useful for comparing request classes and checking regressions, not for inferring guaranteed deployment latency. diff --git a/docs/partner-eval/try-the-api.md b/docs/partner-eval/try-the-api.md index 11f6252..4c578aa 100644 --- a/docs/partner-eval/try-the-api.md +++ b/docs/partner-eval/try-the-api.md @@ -69,13 +69,13 @@ Revocation is part of the public contract, but it requires issuer authorization ## Recent Verification Timing -Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`. For a fuller evaluator-facing summary, see [benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md). -- `POST /api/v1/verify` clean-path latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` -- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` -- `GET /api/v1/receipt/:receiptId` lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` -- `POST /api/v1/receipt/:receiptId/verify` later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` -- tampered artifact detection path: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` +- `POST /api/v1/verify` clean-path latency: mean `5.24 ms`, median `4.11 ms`, p95 `21.65 ms` +- signed receipt generation latency: mean `0.34 ms`, median `0.32 ms`, p95 `0.63 ms` +- `GET /api/v1/receipt/:receiptId` lookup latency: mean `0.57 ms`, median `0.56 ms`, p95 `0.63 ms` +- `POST /api/v1/receipt/:receiptId/verify` later verification latency: mean `0.77 ms`, median `0.71 ms`, p95 `1.08 ms` +- tampered artifact detection path: mean `7.76 ms`, median `5.13 ms`, p95 `42.82 ms` These numbers come from a recent local benchmark harness run against the current evaluator path. They are current validation data, not guaranteed service latency. From 0c4f82b3ad20140ba46ecacf0450f77e392cb75c Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 19:05:28 -0500 Subject: [PATCH 10/25] docs: apply branded documentation templates and standardize structure --- README.md | 48 ++++- docs/README.md | 45 ++++- docs/partner-eval/benchmark-summary.md | 46 ++++- docs/partner-eval/overview.md | 47 ++++- docs/partner-eval/security-summary.md | 52 ++++- docs/partner-eval/try-the-api.md | 47 +++++ docs/security-summary.md | 39 +++- docs/templates/doc-template.md | 54 ++++++ docs/templates/docs-architecture.md | 236 +++++++++++++++++++++++ docs/templates/partner-brief-template.md | 54 ++++++ docs/verification-lifecycle.md | 59 +++++- wiki/API-Overview.md | 57 ++++-- wiki/Claims-Boundary.md | 48 ++++- wiki/Home.md | 65 +++++-- wiki/Quick-Verification-Example.md | 71 +++++-- wiki/What-is-TrustSignal.md | 71 +++++-- 16 files changed, 929 insertions(+), 110 deletions(-) create mode 100644 docs/templates/doc-template.md create mode 100644 docs/templates/docs-architecture.md create mode 100644 docs/templates/partner-brief-template.md diff --git a/README.md b/README.md index 3aef3ee..4392d11 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,44 @@ Website: https://trustsignal.dev TrustSignal is evidence integrity infrastructure for existing workflows. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability without replacing the upstream system of record. -## Problem +Short description: +This repository is the main TrustSignal documentation and implementation surface for public evaluation, existing workflow integration, and signed verification receipts with later verification. + +Audience: +- evaluators +- developers +- partner reviewers + +## Start Here + +- [Documentation index](/Users/christopher/Projects/trustsignal/docs/README.md) +- [Partner evaluation overview](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [Verification lifecycle](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [API overview](/Users/christopher/Projects/trustsignal/wiki/API-Overview.md) +- [Claims boundary](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) + +## Problem / Context High-stakes document and evidence workflows create an attack surface after collection, not just at intake. Once an artifact has been uploaded, reviewed, or approved, downstream teams still face risks such as tampered evidence, provenance loss, artifact substitution, and stale evidence that can no longer be verified later. Those risks matter in audit, compliance, partner-review, and trust-sensitive workflows because the evidence is often challenged after collection rather than at the moment it first entered the system. TrustSignal is designed for workflows where later auditability matters because the artifact, its provenance, or the surrounding workflow record may be questioned later. -## Verification Lifecycle +## Integrity Model The canonical lifecycle diagram and trust-boundary view are documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal accepts a verification request, returns verification signals, issues a signed verification receipt, and supports later verification against stored receipt state so downstream teams can detect artifact tampering, evidence provenance loss, or stale records during audit review. +## How It Works + +At the repository level, TrustSignal shows the public integrity layer through: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration through the public API boundary + ## Demo The fastest evaluator path is the local 5-minute developer trial: @@ -49,7 +75,7 @@ It shows the full lifecycle in one run: See [demo/README.md](/Users/christopher/Projects/trustsignal/demo/README.md). -## Integration Model +## API And Examples Start here if you are evaluating the public verification lifecycle: @@ -66,7 +92,7 @@ Golden path: 3. retrieve the stored receipt 4. run later verification -## Technical Details +## Verification Lifecycle The fastest path in this repository is the public `/api/v1/*` evaluator flow. It is a deliberate evaluator path, not a shortcut around production controls. @@ -90,6 +116,11 @@ The evaluator path is designed to show the core value before full production int - later verification against the stored receipt state - visible handling for tampered evidence or stale evidence through the later verification lifecycle +## Production Considerations + +> [!IMPORTANT] +> Production considerations: local evaluator paths are deliberate evaluation paths. They do not replace deployment-specific authentication, signing configuration, infrastructure controls, or operational review. + ## Local API Development Setup Prerequisites: @@ -161,6 +192,11 @@ TrustSignal is designed to sit behind an existing workflow such as: The upstream platform remains the system of record. TrustSignal adds an integrity layer at the boundary and returns technical verification artifacts that the upstream workflow can store and use later. +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this repository documents the public integration and evaluation surface only. It does not expose proof internals, circuit identifiers, model outputs, signing infrastructure specifics, or internal service topology. + ## Integration Boundary Notes The local evaluator path is intentionally constrained. Local development defaults are a deliberate evaluator and development path, and they fail closed where production trust assumptions are not satisfied. @@ -250,11 +286,13 @@ Recent local benchmark snapshot from [bench/results/latest.md](/Users/christophe This is a recent local evaluator run against the current `/api/v1/*` lifecycle with a temporary local PostgreSQL instance. It is a benchmark snapshot for evaluation and regression tracking, not a production guarantee or SLA. -## Documentation Map +## Related Documentation - [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) - [docs/partner-eval/quickstart.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/quickstart.md) - [docs/partner-eval/api-playground.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) +- [docs/templates/docs-architecture.md](/Users/christopher/Projects/trustsignal/docs/templates/docs-architecture.md) +- [docs/templates/doc-template.md](/Users/christopher/Projects/trustsignal/docs/templates/doc-template.md) - [wiki/What-is-TrustSignal.md](/Users/christopher/Projects/trustsignal/wiki/What-is-TrustSignal.md) - [wiki/API-Overview.md](/Users/christopher/Projects/trustsignal/wiki/API-Overview.md) - [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/README.md b/docs/README.md index 02aaf80..d0c51d3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,17 +1,45 @@ # TrustSignal Documentation Index -This folder is organized into active, canonical documents and archived historical material. +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. -## Problem +Short description: +This index organizes the active TrustSignal documentation set for evaluators, developers, and partner reviewers, with links to lifecycle, API, security, benchmark, and claims-boundary materials. + +Audience: +- evaluators +- developers +- partner reviewers + +## Start Here + +- [Partner evaluation overview](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [Verification lifecycle](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [Security summary](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [Benchmark summary](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) +- [Claims boundary](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) +- [Docs architecture](/Users/christopher/Projects/trustsignal/docs/templates/docs-architecture.md) + +## Problem / Context TrustSignal documentation is written for evaluators and implementers working in workflows where later auditability matters. The main attack surface is not only bad data at intake, but also tampered evidence, provenance loss, artifact substitution, and stale evidence that cannot be verified later. -## Verification Lifecycle +## Integrity Model The canonical lifecycle and trust-boundary diagrams are documented in [verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal is evidence integrity infrastructure. It acts as an integrity layer that returns signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflow integration. +## How It Works + +The documentation set is organized around: + +- overview and start-here materials +- core concepts and verification lifecycle +- API and example documents +- security and claims boundary materials +- benchmarks and partner evaluation materials +- reference and archive material + ## Demo Start with the local developer trial if you want the fastest technical evaluation: @@ -20,7 +48,7 @@ Start with the local developer trial if you want the fastest technical evaluatio The demo shows artifact hashing, verification, signed verification receipt issuance, later verification, and tampered artifact mismatch detection without external services. -## Integration Model +## Partner Evaluation Start here if you want to evaluate the public verification lifecycle quickly: @@ -38,7 +66,7 @@ Golden path: 3. retrieve the stored receipt 4. run later verification -## Integration Fit +## Reference / Related Docs The evaluator and demo paths are deliberate evaluator paths. They show the verification lifecycle safely before production integration and do not remove production security requirements. @@ -82,3 +110,10 @@ Historical planning, synthesized source-of-truth drafts, and early notebook logs - `archive/legacy-2026-02-25/` Use archived files for context only, not as current implementation guidance. + +## Related Documentation + +- [README.md](/Users/christopher/Projects/trustsignal/README.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [docs/security-summary.md](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [docs/templates/docs-architecture.md](/Users/christopher/Projects/trustsignal/docs/templates/docs-architecture.md) diff --git a/docs/partner-eval/benchmark-summary.md b/docs/partner-eval/benchmark-summary.md index eea8664..0d5f721 100644 --- a/docs/partner-eval/benchmark-summary.md +++ b/docs/partner-eval/benchmark-summary.md @@ -1,5 +1,15 @@ # TrustSignal Evaluator Benchmark Summary +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +This evaluator-facing brief summarizes the latest local TrustSignal benchmark snapshot, scenario coverage, benchmark metadata, and the right way to interpret the numbers. + +Audience: +- partner evaluators +- technical sponsors +- developers validating benchmark artifacts + This page summarizes the most recent local evaluator benchmark snapshot from [bench/results/latest.json](/Users/christopher/Projects/trustsignal/bench/results/latest.json) and [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md). ## Executive Summary @@ -8,7 +18,17 @@ The current local evaluator run shows that the public `/api/v1/*` lifecycle is f The tampered artifact path also completed successfully across all sampled runs, with a median of `5.13 ms`. Its `42.82 ms` p95 is materially higher than the median and should be treated as a follow-up item rather than a normal steady-state expectation. The current evidence suggests local first-run or parser-path variance, not a correctness failure, but the spread is large enough to call out explicitly. -## Metric Table +## Key Facts / Scope + +- Scope: current local evaluator benchmark run against the public `/api/v1/*` lifecycle +- Primary sample size: `15` iterations per applicable scenario +- Sequential batch size: `10` +- Raw artifacts: [latest.json](/Users/christopher/Projects/trustsignal/bench/results/latest.json), [latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) +- Integrity layer focus: signed verification receipts, verification signals, verifiable provenance, later verification, and existing workflow integration + +## Main Content + +### Metric Table | Metric | Samples | Min (ms) | Max (ms) | Mean (ms) | Median (ms) | p95 (ms) | | --- | ---: | ---: | ---: | ---: | ---: | ---: | @@ -19,7 +39,7 @@ The tampered artifact path also completed successfully across all sampled runs, | Tampered artifact detection latency | 15 | 4.74 | 42.82 | 7.76 | 5.13 | 42.82 | | Repeated-run stability latency | 15 | 3.03 | 3.69 | 3.24 | 3.16 | 3.69 | -## Scenario Coverage +### Scenario Coverage - `clean`: end-to-end `POST /api/v1/verify` with signed receipt issuance - `tampered`: declared-hash vs observed-digest mismatch path @@ -38,7 +58,7 @@ Reliability notes from the latest run: - `15/15` later verification requests returned `verified=true`. - `10/10` sequential batch requests returned `HTTP 200`. -## Environment And Caveats +### Environment And Caveats - Benchmark timestamp: `2026-03-12T22:30:04.260Z` - Runtime: Node `v22.14.0` @@ -54,13 +74,13 @@ Current caveats: - The harness uses Fastify injection to exercise the public evaluator routes without adding external network-hop latency. - The tampered scenario uses a local byte fixture to force a declared-hash mismatch. It is useful for evaluator behavior checks, not for claiming document-parser completeness. -## How To Interpret These Numbers +### How To Interpret These Numbers Treat these values as recent local evaluator benchmark results. They are useful for comparing request classes, spotting regressions, and demonstrating lifecycle behavior, but they are not production SLA claims and should not be read as guaranteed deployment latency. The medians are the best quick read for typical local behavior. The p95 values are more useful for spotting variance and warm-up effects. In this run, the tampered-path p95 spike is large enough to watch in future snapshots. -## What The Benchmark Does Measure +### What The Benchmark Does Measure - Public evaluator lifecycle timing for the current `/api/v1/*` verification path - Signed receipt issuance timing using the same receipt-building and signing primitives used by the evaluator flow @@ -69,7 +89,7 @@ The medians are the best quick read for typical local behavior. The p95 values a - API-boundary failure behavior for bad auth and malformed payloads - A safe local fail-closed dependency scenario -## What The Benchmark Does Not Measure +### What The Benchmark Does Not Measure - Production network latency, cold starts behind hosting infrastructure, or edge routing effects - Multi-tenant concurrency, sustained throughput, or horizontal scaling behavior @@ -77,6 +97,18 @@ The medians are the best quick read for typical local behavior. The p95 values a - Full end-user browser timing - Proof internals, signer infrastructure specifics, internal topology, or any non-public runtime surfaces -## Tampered Path Variance Review +### Tampered Path Variance Review The tampered artifact path recorded a median of `5.13 ms` but a p95 of `42.82 ms`. Given the local fixture-driven setup and the parser/compliance code touched by that path, this looks more like local path variance than an indication that tamper detection is unreliable. Even so, the spread is large enough that it should be treated as a follow-up item in future benchmark runs rather than dismissed as unimportant expected variance. + +## Claims Boundary + +> [!NOTE] +> Claims boundary: this brief describes local benchmark behavior for the public TrustSignal integration surface. It should not be read as a production SLA, a claim about internal proof systems, or a statement about non-public infrastructure. + +## Related Artifacts / References + +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [docs/partner-eval/try-the-api.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) +- [docs/partner-eval/security-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/security-summary.md) +- [bench/README.md](/Users/christopher/Projects/trustsignal/bench/README.md) diff --git a/docs/partner-eval/overview.md b/docs/partner-eval/overview.md index 1497427..b61f5c8 100644 --- a/docs/partner-eval/overview.md +++ b/docs/partner-eval/overview.md @@ -1,10 +1,20 @@ # TrustSignal Partner Evaluation Overview -## Problem +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +This overview is the evaluator-facing entry point for the TrustSignal integrity layer, public lifecycle, benchmark materials, and existing workflow integration path. + +Audience: +- partner evaluators +- solutions engineers +- technical sponsors + +## Problem / Context Teams often have a workflow record that says an artifact was reviewed, approved, or submitted, but they cannot easily prove later that the same artifact is still the one tied to that decision. In high-loss and highly scrutinized workflows, that creates an attack surface around tampered evidence, provenance loss, artifact substitution, and stale evidence in later review paths. -## Verification Lifecycle +## Integrity Model The canonical lifecycle diagram and trust-boundary diagram are documented in [../verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). @@ -17,6 +27,16 @@ TrustSignal is designed to support: - verifiable provenance - later verification without replacing the upstream workflow owner +## How It Works + +TrustSignal supports evaluator review through: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration through the public API boundary + ## Demo Start with the local developer trial when you want the shortest path to the verification lifecycle: @@ -25,7 +45,7 @@ Start with the local developer trial when you want the shortest path to the veri - [Evaluator start here](/Users/christopher/Projects/trustsignal/docs/partner-eval/start-here.md) - [Try the API](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) -## Integration Model +## Partner Evaluation Start with these evaluator assets: @@ -37,7 +57,7 @@ Start with these evaluator assets: The evaluator flow is designed to show the verification lifecycle safely before production integration requirements are introduced. -## Technical Details +## API And Examples The public evaluation path in this repository is the `/api/v1/*` surface: @@ -49,7 +69,7 @@ The public evaluation path in this repository is the `/api/v1/*` surface: Canonical contract and payload examples live in [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml) and the [`examples/`](../../examples) directory. -## Current Evaluator Metrics +## Benchmarks And Evaluator Materials Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`. For evaluator-facing interpretation and caveats, see [benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md). @@ -62,6 +82,11 @@ Recent local benchmark snapshot from [bench/results/latest.md](/Users/christophe This snapshot comes from a recent local evaluator run. It is useful for comparing request classes and checking regressions, not for inferring guaranteed deployment latency. +## Production Considerations + +> [!IMPORTANT] +> Production considerations: the evaluator path demonstrates the TrustSignal integrity layer before full deployment configuration. It does not replace deployment-specific authentication, signing configuration, or infrastructure review. + ## Integration Fit TrustSignal fits behind an existing workflow such as: @@ -73,6 +98,18 @@ TrustSignal fits behind an existing workflow such as: The upstream platform remains the system of record. TrustSignal adds an integrity layer and returns technical verification artifacts that can be stored alongside the workflow record. +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this overview covers the public evaluation surface only. It does not expose proof internals, circuit identifiers, model outputs, signing infrastructure specifics, or internal service topology. + ## Production Deployment Requirements Local and evaluator paths are deliberate evaluator paths. Production deployment requires explicit authentication, signing configuration, and environment setup. Fail-closed defaults are part of the security posture and are intended to stop unsafe production assumptions from being applied implicitly. + +## Related Documentation + +- [docs/partner-eval/try-the-api.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) +- [docs/partner-eval/benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) +- [docs/partner-eval/security-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/security-summary.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) diff --git a/docs/partner-eval/security-summary.md b/docs/partner-eval/security-summary.md index c8015e4..35f5d94 100644 --- a/docs/partner-eval/security-summary.md +++ b/docs/partner-eval/security-summary.md @@ -1,14 +1,46 @@ # Partner Security Summary -## Problem +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +This partner-facing security summary explains the public TrustSignal integration boundary, key controls, and claims boundary for existing workflow integration. + +Audience: +- partner security reviewers +- evaluators +- technical sponsors + +## Executive Summary + +TrustSignal exposes a public integration boundary built around signed verification receipts, verification signals, verifiable provenance, and later verification. The public security posture is focused on route-level authorization, explicit lifecycle controls, and fail-closed defaults without exposing non-public implementation details. + +## Key Facts / Scope + +- Scope: public `/api/v1/*` integration boundary +- Focus: evaluator-safe security posture +- Out of scope: internal implementation details and deployment-specific infrastructure controls + +## Main Content + +### Problem / Context Partners need enough security detail to evaluate the integration boundary without exposing internal implementation details that are not required for integration. -## Integrity Model +### Integrity Model TrustSignal provides a public API boundary that is centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. -## Integration Fit +### How It Works + +TrustSignal public security materials focus on: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration + +### Integration Fit For the public `/api/v1/*` surface in this repository: @@ -17,7 +49,12 @@ For the public `/api/v1/*` surface in this repository: - Request validation, rate limiting, and structured service logging are implemented at the API gateway. - Receipt revocation requires additional issuer authorization headers. -## Technical Detail +### Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this summary covers the public-safe integration surface only. It does not expose proof internals, signer infrastructure specifics, internal topology, or unsupported legal/compliance claims. + +### Technical Detail TrustSignal does not require partners to understand internal proof systems, internal service topology, or signing infrastructure details in order to integrate. @@ -29,3 +66,10 @@ For public evaluation, the important security properties are: - authorization boundaries are explicit at the route level Operational deployment details such as environment-specific key custody, hosting controls, and external provider posture remain infrastructure concerns outside the public integration contract. + +## Related Artifacts / References + +- [docs/security-summary.md](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) +- [SECURITY_CHECKLIST.md](/Users/christopher/Projects/trustsignal/SECURITY_CHECKLIST.md) diff --git a/docs/partner-eval/try-the-api.md b/docs/partner-eval/try-the-api.md index 4c578aa..352d067 100644 --- a/docs/partner-eval/try-the-api.md +++ b/docs/partner-eval/try-the-api.md @@ -1,7 +1,33 @@ # Try The TrustSignal API +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +This page is the copy-paste API trial path for the public TrustSignal evaluator contract and existing workflow integration flow. + +Audience: +- integration engineers +- evaluators +- developers + This page is the copy-paste API trial path for the public evaluator contract. +## Problem / Context + +Evaluators and developers need a compact path to see how verification signals, signed verification receipts, verifiable provenance, and later verification work together at the API boundary. + +## Integrity Model + +The public evaluator path demonstrates: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration + +## How It Works + ## 1. Submit A Verification Request Request body: [verification-request.json](/Users/christopher/Projects/trustsignal/examples/verification-request.json) @@ -67,6 +93,10 @@ curl -X POST "$TRUSTSIGNAL_BASE_URL/api/v1/anchor/$RECEIPT_ID" \ Revocation is part of the public contract, but it requires issuer authorization headers in addition to the API key. Use the Postman collection for the full request template. +## Example Or Diagram + +The request and response examples below show the public evaluator flow from verification request to later verification. + ## Recent Verification Timing Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`. For a fuller evaluator-facing summary, see [benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md). @@ -79,9 +109,26 @@ Recent local benchmark snapshot from [bench/results/latest.md](/Users/christophe These numbers come from a recent local benchmark harness run against the current evaluator path. They are current validation data, not guaranteed service latency. +## Production Considerations + +> [!IMPORTANT] +> Production considerations: use this evaluator flow as a technical trial path, not as a complete production deployment checklist. + ## Production Readiness - Authentication: use `x-api-key` with the scopes required for verify, read, anchor, or revoke operations. - Environment configuration: separate local, staging, and production base URLs, API keys, and lifecycle identifiers. - Lifecycle monitoring: monitor receipt retrieval, lifecycle state changes, and later verification outcomes in the surrounding workflow. - Verification checks before relying on prior results: run later verification before audit review, handoff, or another high-trust workflow step. + +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this page documents the public evaluator contract only. It does not expose proof internals, signer infrastructure specifics, internal topology, or unsupported performance guarantees. + +## Related Documentation + +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [docs/partner-eval/benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/security-summary.md b/docs/security-summary.md index 91a2d73..2cc6c36 100644 --- a/docs/security-summary.md +++ b/docs/security-summary.md @@ -1,6 +1,16 @@ # TrustSignal Public Security Summary -## Problem +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +This public-safe security summary explains the TrustSignal integration boundary, security posture, and claims boundary without exposing non-public implementation details. + +Audience: +- partner security reviewers +- evaluators +- developers + +## Problem / Context Partners and evaluators need a public-safe security summary that explains the attack surface without exposing internal implementation details. In high-stakes workflows, evidence can be challenged after collection through tampered evidence, provenance loss, artifact substitution, or stale records that are no longer independently verifiable. @@ -8,6 +18,16 @@ Partners and evaluators need a public-safe security summary that explains the at TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability for existing workflow integration. +## How It Works + +TrustSignal public security materials focus on the integration-facing integrity layer: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration + ## Integration Fit For the public `/api/v1/*` surface in this repository: @@ -20,10 +40,20 @@ For the public `/api/v1/*` surface in this repository: Evaluator and demo flows are deliberate evaluator paths. They are designed to show the verification lifecycle safely before production integration. +## Production Considerations + +> [!IMPORTANT] +> Production considerations: public evaluator documentation does not replace environment-specific authentication, signing configuration, hosting controls, secret management, or operational review. + ## Production Deployment Requirements Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this summary covers the public-safe security posture only. It does not expose proof internals, signing infrastructure specifics, internal service topology, or unsupported legal/compliance claims. + ## Technical Detail TrustSignal public materials should be understood within this boundary: @@ -43,3 +73,10 @@ TrustSignal does not provide: - fraud adjudication - a replacement for the system of record - infrastructure claims that depend on deployment-specific evidence outside this repository + +## Related Documentation + +- [docs/partner-eval/security-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/security-summary.md) +- [SECURITY_CHECKLIST.md](/Users/christopher/Projects/trustsignal/SECURITY_CHECKLIST.md) +- [docs/SECURITY.md](/Users/christopher/Projects/trustsignal/docs/SECURITY.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/templates/doc-template.md b/docs/templates/doc-template.md new file mode 100644 index 0000000..e8de47e --- /dev/null +++ b/docs/templates/doc-template.md @@ -0,0 +1,54 @@ +# Document Title + +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +One or two sentences explaining what this document covers and how it supports existing workflow integration. + +Audience: +- evaluators +- developers +- partner reviewers + +## Problem / Context + +Explain the reader problem first. Keep the framing aligned to evidence integrity infrastructure, signed verification receipts, verification signals, verifiable provenance, later verification, and existing workflow integration. + +## Integrity Model + +Describe how this document relates to the TrustSignal integrity layer. Prefer the canonical terms: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- integrity layer +- existing workflow integration + +## How It Works + +Provide the practical explanation. Use short subsections or steps when useful. + +## Example Or Diagram + +Add a code example, lifecycle bullets, or a Mermaid diagram when that helps the reader. + +## Production Considerations + +> [!IMPORTANT] +> Production considerations: local evaluator paths are not substitutes for deployment-specific authentication, signing configuration, infrastructure controls, and operational review. + +List production-sensitive considerations that belong in this document. + +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this document describes the public integration and evaluation surface only. Do not read it as a claim about internal proof systems, signer infrastructure, internal topology, or environment-specific controls outside this repository. + +Document the relevant public-safe security and claims boundary notes. + +## Related Documentation + +- [Related doc one](/Users/christopher/Projects/trustsignal/docs/README.md) +- [Related doc two](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [Related doc three](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/templates/docs-architecture.md b/docs/templates/docs-architecture.md new file mode 100644 index 0000000..2764a88 --- /dev/null +++ b/docs/templates/docs-architecture.md @@ -0,0 +1,236 @@ +# TrustSignal Documentation Architecture + +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +This guide defines the canonical information architecture for TrustSignal documentation. It is designed for GitHub markdown today and for later mirroring into website documentation with minimal restructuring. + +## Purpose + +TrustSignal documentation should make it easy to understand: + +- what TrustSignal is +- how the integrity layer fits into existing workflow integration +- how signed verification receipts, verification signals, verifiable provenance, and later verification relate to one another +- where evaluators, developers, and partner reviewers should start +- what claims are in scope and what remains outside the public boundary + +## Canonical Sections + +### 1. Overview / Start Here + +Audience: +- new evaluators +- partner reviewers +- developers new to the repository + +Content: +- product-level orientation +- short repository description +- start-here navigation +- reading order for first-time readers +- high-level explanation of existing workflow integration + +Examples: +- `README.md` +- `docs/README.md` +- `docs/partner-eval/start-here.md` +- `wiki/Home.md` + +### 2. Core Concepts + +Audience: +- evaluators +- product and partnership stakeholders +- developers who need the terminology before the API details + +Content: +- evidence integrity infrastructure +- integrity layer positioning +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration framing + +Examples: +- `wiki/What-is-TrustSignal.md` +- `wiki/Verification-Receipts.md` + +### 3. Verification Lifecycle + +Audience: +- evaluators +- implementation owners +- security reviewers + +Content: +- lifecycle diagrams +- step-by-step explanations +- trust boundary framing +- how the receipt lifecycle works from request through later verification + +Examples: +- `docs/verification-lifecycle.md` +- `wiki/Quick-Verification-Example.md` + +### 4. API and Examples + +Audience: +- developers +- integration engineers +- technical evaluators + +Content: +- public endpoint overview +- request and response examples +- auth expectations +- lifecycle actions +- error semantics + +Examples: +- `openapi.yaml` +- `docs/partner-eval/try-the-api.md` +- `wiki/API-Overview.md` + +### 5. Security and Threat Model + +Audience: +- security reviewers +- partner security teams +- technical decision-makers + +Content: +- public-safe security posture +- security controls at the integration boundary +- what is intentionally not exposed +- threat model links +- production security considerations + +Examples: +- `docs/security-summary.md` +- `docs/partner-eval/security-summary.md` +- `SECURITY_CHECKLIST.md` +- `docs/SECURITY.md` + +### 6. Benchmarks and Evaluator Materials + +Audience: +- evaluators +- partner technical reviewers +- internal teams validating performance snapshots + +Content: +- benchmark methodology +- benchmark metadata +- scenario coverage +- local benchmark caveats +- links to raw artifacts + +Examples: +- `bench/README.md` +- `docs/partner-eval/benchmark-summary.md` +- `bench/results/latest.md` + +### 7. Partner Evaluation + +Audience: +- partner evaluators +- solutions engineers +- technical sponsors + +Content: +- overview of evaluator path +- benchmark summary +- security summary +- quickstart links +- integration briefing materials + +Examples: +- `docs/partner-eval/overview.md` +- `docs/partner-eval/try-the-api.md` +- `docs/partner-eval/benchmark-summary.md` + +### 8. Claims Boundary + +Audience: +- partner reviewers +- legal/compliance-adjacent reviewers +- internal authors of public docs + +Content: +- what TrustSignal does claim +- what TrustSignal does not claim +- phrasing guardrails +- public/private boundary references + +Examples: +- `wiki/Claims-Boundary.md` +- `docs/public-private-boundary.md` +- `README.md` claims sections + +### 9. Reference / Related Docs + +Audience: +- all readers once they need depth + +Content: +- related document lists +- archival references +- legal, policy, and operational references +- specialized evaluator materials + +Examples: +- `docs/README.md` +- related documentation sections across public docs + +## Linking Model + +TrustSignal docs should link in layers: + +1. Overview documents link down into lifecycle, API, security, benchmarks, and claims boundary. +2. Concept and lifecycle docs link sideways to API examples, security summaries, and evaluator materials. +3. Deep technical or evaluator docs link back up to overview/start-here pages so readers do not dead-end. + +Preferred link behavior: + +- Every public-facing doc should end with a `Related Documentation` section. +- API docs should link to lifecycle, evaluator overview, and claims boundary. +- Security docs should link to claims boundary and public-safe architecture docs. +- Benchmark docs should link to evaluator overview, API trial docs, and raw benchmark artifacts. + +## Preferred Reading Order + +### Evaluator Reading Order + +1. `README.md` +2. `docs/partner-eval/overview.md` +3. `docs/verification-lifecycle.md` +4. `docs/partner-eval/try-the-api.md` +5. `docs/partner-eval/benchmark-summary.md` +6. `docs/partner-eval/security-summary.md` +7. `wiki/Claims-Boundary.md` + +### Developer Reading Order + +1. `README.md` +2. `docs/README.md` +3. `docs/verification-lifecycle.md` +4. `wiki/API-Overview.md` +5. `docs/partner-eval/try-the-api.md` +6. `docs/security-summary.md` +7. `bench/README.md` + +## Authoring Rules + +- Lead with a short description and audience label where useful. +- Use the canonical TrustSignal phrases consistently: + - evidence integrity infrastructure + - signed verification receipts + - verification signals + - verifiable provenance + - later verification + - integrity layer + - existing workflow integration +- Keep GitHub-markdown-friendly structure. +- Do not expose internal proof systems, circuit identifiers, model outputs, signing infrastructure specifics, internal service topology, witness/prover details, or registry scoring algorithms. +- Do not force identical headings into every file when the result would reduce clarity. Use the common structure intelligently. diff --git a/docs/templates/partner-brief-template.md b/docs/templates/partner-brief-template.md new file mode 100644 index 0000000..e88b68e --- /dev/null +++ b/docs/templates/partner-brief-template.md @@ -0,0 +1,54 @@ +# TrustSignal Brief Title + +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +One or two sentences that frame the brief for partner evaluators, technical sponsors, or security reviewers. + +Audience: +- partner evaluators +- technical decision-makers +- integration leads + +## Executive Summary + +Summarize the main conclusion in one short paragraph. + +## Key Facts / Scope + +- Scope item one +- Scope item two +- Scope item three +- Scope limitation or key boundary + +## Main Content + +Use the sections that fit the brief: + +### Context + +Explain why the brief exists. + +### Current State + +Summarize the current TrustSignal position, benchmark, security posture, or integration state. + +### Evidence Or Examples + +Add the supporting details, table, example, or diagram. + +## Production Considerations + +> [!IMPORTANT] +> Production considerations: this brief supports evaluation and existing workflow integration planning. It does not replace deployment-specific authentication, signing, infrastructure, or operational review. + +## Claims Boundary + +> [!NOTE] +> Claims boundary: TrustSignal public briefs describe the integrity layer, signed verification receipts, verification signals, verifiable provenance, and later verification. They do not expose proof internals, model outputs, signer infrastructure specifics, internal service topology, or unsupported legal/compliance claims. + +## Related Artifacts / References + +- [Related artifact one](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [Related artifact two](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [Related artifact three](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/verification-lifecycle.md b/docs/verification-lifecycle.md index f6cbf96..54f8430 100644 --- a/docs/verification-lifecycle.md +++ b/docs/verification-lifecycle.md @@ -1,8 +1,33 @@ # TrustSignal Verification Lifecycle +> TrustSignal is evidence integrity infrastructure for signed verification receipts and later verification. + +Short description: +This document explains the externally visible TrustSignal verification lifecycle for verification signals, signed verification receipts, verifiable provenance, and later verification in existing workflow integration. + +Audience: +- evaluators +- developers +- security reviewers + TrustSignal is evidence integrity infrastructure for existing workflows. The verification lifecycle below shows the externally visible flow for producing verification signals, issuing signed verification receipts, and supporting later verification without exposing private verification engine internals. -## Lifecycle Diagram +## Problem / Context + +Partner workflows often need to rely on evidence after collection, not just at intake. The lifecycle matters because later verification is where tampered evidence, provenance loss, artifact substitution, and stale records become visible. + +## Integrity Model + +TrustSignal acts as an integrity layer around an existing system of record. It returns: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification capability + +## Example Or Diagram + +### Lifecycle Diagram ```mermaid flowchart TD @@ -24,7 +49,9 @@ flowchart TD G --> H ``` -## Step Explanations +## How It Works + +### Step Explanations ### 1. Artifact or Evidence @@ -58,7 +85,12 @@ Before relying on the earlier result during audit review, partner review, or ano If the current artifact or stored state no longer matches the receipt-bound record, later verification produces a mismatch signal that exposes tampering, substitution, or provenance drift. -## Trust Boundary Diagram +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this lifecycle describes the public TrustSignal integration surface. It does not document proof internals, signer infrastructure specifics, internal service topology, or non-public runtime details. + +### Trust Boundary Diagram ```mermaid flowchart TD @@ -72,7 +104,7 @@ flowchart TD C --> D ``` -## Boundary Explanation +### Boundary Explanation - The external workflow or partner system remains the system of record. - The TrustSignal API Gateway is the public integration boundary for verification and later verification requests. @@ -81,12 +113,19 @@ flowchart TD ## Current Evaluator Metrics -Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`: -- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` -- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` -- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` -- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` -- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` +- clean verification request latency: mean `5.24 ms`, median `4.11 ms`, p95 `21.65 ms` +- signed receipt generation latency: mean `0.34 ms`, median `0.32 ms`, p95 `0.63 ms` +- receipt lookup latency: mean `0.57 ms`, median `0.56 ms`, p95 `0.63 ms` +- later verification latency: mean `0.77 ms`, median `0.71 ms`, p95 `1.08 ms` +- tampered artifact detection latency: mean `7.76 ms`, median `5.13 ms`, p95 `42.82 ms` This benchmark snapshot is from a recent local evaluator run using the current public lifecycle. It helps characterize the flow in this repository without making production-performance claims. + +## Related Documentation + +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [docs/partner-eval/try-the-api.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) +- [docs/partner-eval/benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/wiki/API-Overview.md b/wiki/API-Overview.md index 18fb696..f8f4629 100644 --- a/wiki/API-Overview.md +++ b/wiki/API-Overview.md @@ -1,33 +1,51 @@ **Navigation** -- [Home](Home) -- [What is TrustSignal](What-is-TrustSignal) -- [Architecture](Evidence-Integrity-Architecture) -- [Verification Receipts](Verification-Receipts) -- [API Overview](API-Overview) -- [Claims Boundary](Claims-Boundary) -- [Quick Verification Example](Quick-Verification-Example) -- [Vanta Integration Example](Vanta-Integration-Example) +- [Home](Home.md) +- [What is TrustSignal](What-is-TrustSignal.md) +- [Architecture](Evidence-Integrity-Architecture.md) +- [Verification Receipts](Verification-Receipts.md) +- [API Overview](API-Overview.md) +- [Claims Boundary](Claims-Boundary.md) +- [Quick Verification Example](Quick-Verification-Example.md) +- [Vanta Integration Example](Vanta-Integration-Example.md) # API Overview -## Problem +Short description: +This page summarizes the TrustSignal public API surface for signed verification receipts, verification signals, verifiable provenance, later verification, and existing workflow integration. + +Audience: +- integration engineers +- evaluators +- developers + +## Problem / Context Partners need a stable public contract that explains how TrustSignal fits into an existing workflow without requiring them to understand internal implementation details. The relevant attack surface includes evidence tampering after collection, artifact substitution attacks, provenance loss in compliance workflows, stale evidence during audit review, and documentation chains that cannot be verified later. -## Verification Lifecycle +## Integrity Model The canonical lifecycle diagram is documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal exposes a public verification lifecycle centered on signed verification receipts, verification signals, verifiable provenance metadata, and later verification. +## How It Works + +The public lifecycle is centered on: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration + ## Demo Start with the local developer trial for the fastest lifecycle walkthrough: - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) -## Integration Model +## API And Examples Start here to try the public lifecycle: @@ -63,6 +81,11 @@ Golden path: - `x-signature-timestamp` - `x-issuer-signature` +## Production Considerations + +> [!IMPORTANT] +> Production considerations: the public API overview is an integration reference, not a substitute for deployment-specific authentication, signing configuration, infrastructure review, or operational controls. + ## Integration Fit The integration-facing `/api/v1/*` surface is the main public partner API in this repository. It uses `x-api-key` authentication with scoped access such as `verify`, `read`, `anchor`, and `revoke`. @@ -92,6 +115,11 @@ Local development defaults are intentionally constrained and fail closed where p | `GET` | `/v1/status/:bundleId` | bearer JWT | Check bundle status | | `POST` | `/v1/revoke` | bearer JWT with admin authorization | Revoke a bundle | +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this page documents the public API contract and existing workflow integration surface only. It does not expose proof internals, signer infrastructure specifics, internal topology, or unsupported performance/security claims. + ### Error Semantics Integrators should expect these broad patterns: @@ -104,3 +132,10 @@ Integrators should expect these broad patterns: - `503` when a required dependency is unavailable The canonical public contract for the verification lifecycle is [openapi.yaml](/Users/christopher/Projects/trustsignal/openapi.yaml). + +## Related Documentation + +- [docs/partner-eval/try-the-api.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/wiki/Claims-Boundary.md b/wiki/Claims-Boundary.md index 9f33d66..47b34ed 100644 --- a/wiki/Claims-Boundary.md +++ b/wiki/Claims-Boundary.md @@ -1,17 +1,25 @@ **Navigation** -- [Home](Home) -- [What is TrustSignal](What-is-TrustSignal) -- [Architecture](Evidence-Integrity-Architecture) -- [Verification Receipts](Verification-Receipts) -- [API Overview](API-Overview) -- [Claims Boundary](Claims-Boundary) -- [Quick Verification Example](Quick-Verification-Example) -- [Vanta Integration Example](Vanta-Integration-Example) +- [Home](Home.md) +- [What is TrustSignal](What-is-TrustSignal.md) +- [Architecture](Evidence-Integrity-Architecture.md) +- [Verification Receipts](Verification-Receipts.md) +- [API Overview](API-Overview.md) +- [Claims Boundary](Claims-Boundary.md) +- [Quick Verification Example](Quick-Verification-Example.md) +- [Vanta Integration Example](Vanta-Integration-Example.md) # Claims Boundary -## Problem +Short description: +This page defines what TrustSignal public materials do and do not claim across signed verification receipts, verification signals, verifiable provenance, later verification, and existing workflow integration. + +Audience: +- evaluators +- partner reviewers +- documentation authors + +## Problem / Context Public integrations need a clear technical boundary so partner engineers and reviewers know what the TrustSignal response means and what it does not mean. @@ -25,10 +33,25 @@ TrustSignal is evidence integrity infrastructure. It acts as an integrity layer - later verification capability - API-accessible receipt lifecycle state +## How It Works + +The public TrustSignal position should be read through the integrity layer: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration + ## Integration Fit TrustSignal is designed to sit behind an upstream workflow that remains the system of record. The partner or workflow owner keeps control of collection, review, and business decisions. +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: public TrustSignal documents describe technical verification artifacts and the integrity layer. They do not create legal determinations, compliance certifications, or environment-specific infrastructure guarantees. + ## Technical Detail TrustSignal does not provide: @@ -40,3 +63,10 @@ TrustSignal does not provide: - guarantees that depend on environment-specific infrastructure evidence outside this repository The TrustSignal response should be treated as a technical verification artifact that supports audit-ready evidence and later verification. + +## Related Documentation + +- [README.md](/Users/christopher/Projects/trustsignal/README.md) +- [docs/security-summary.md](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [wiki/What-is-TrustSignal.md](/Users/christopher/Projects/trustsignal/wiki/What-is-TrustSignal.md) diff --git a/wiki/Home.md b/wiki/Home.md index 0ff9b5b..e70e6b8 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -1,43 +1,60 @@ **Navigation** - [Home](Home) -- [What is TrustSignal](What-is-TrustSignal) -- [Architecture](Evidence-Integrity-Architecture) -- [Verification Receipts](Verification-Receipts) -- [API Overview](API-Overview) -- [Claims Boundary](Claims-Boundary) -- [Quick Verification Example](Quick-Verification-Example) -- [Vanta Integration Example](Vanta-Integration-Example) +- [What is TrustSignal](What-is-TrustSignal.md) +- [Architecture](Evidence-Integrity-Architecture.md) +- [Verification Receipts](Verification-Receipts.md) +- [API Overview](API-Overview.md) +- [Claims Boundary](Claims-Boundary.md) +- [Quick Verification Example](Quick-Verification-Example.md) +- [Vanta Integration Example](Vanta-Integration-Example.md) # TrustSignal Wiki +Short description: +This wiki is the lightweight TrustSignal knowledge map for core concepts, API orientation, claims boundary, and quick evaluator references. + +Audience: +- evaluators +- developers +- partner reviewers + TrustSignal is evidence integrity infrastructure for existing workflows. It acts as an integrity layer that provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability. -## Problem +## Start Here + +- [What is TrustSignal](What-is-TrustSignal.md) +- [API Overview](API-Overview.md) +- [Quick Verification Example](Quick-Verification-Example.md) +- [Claims Boundary](Claims-Boundary.md) + +## Problem / Context TrustSignal is built for workflows where evidence can be challenged after collection. The relevant attack surface includes evidence tampering after collection, artifact substitution attacks, provenance loss across compliance workflows, stale evidence during audit review, and documentation chains that cannot be verified later. High-loss environments create incentives for these attack paths because downstream reviewers often must rely on artifacts long after the original collection event. -## Verification Lifecycle +## Integrity Model The canonical lifecycle diagram is documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). TrustSignal provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification capability as an integrity layer for an existing system of record. -## Start Here +## How It Works -- [What is TrustSignal](What-is-TrustSignal) -- [API Overview](API-Overview) -- [Verification Receipts](Verification-Receipts) -- [Claims Boundary](Claims-Boundary) -- [Quick Verification Example](Quick-Verification-Example) +TrustSignal provides: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration ## Demo - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) -## Integration Model +## API And Examples Use the evaluator docs when you want to see the verification lifecycle before production integration detail: @@ -45,7 +62,7 @@ Use the evaluator docs when you want to see the verification lifecycle before pr - [API playground](/Users/christopher/Projects/trustsignal/docs/partner-eval/api-playground.md) - [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) -## Technical Details +## Verification Lifecycle The public verification lifecycle is: @@ -55,10 +72,22 @@ The public verification lifecycle is: 4. run later verification before downstream reliance 5. use authorized lifecycle actions when receipt state changes +## Production Considerations + +> [!IMPORTANT] +> Production considerations: the wiki mirrors the public evaluation surface. It does not replace deployment-specific authentication, signing configuration, infrastructure controls, or operational review. + ## Production Deployment Requirements Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. -## Current Boundary +## Security And Claims Boundary TrustSignal provides technical verification artifacts. It does not provide legal determinations, compliance certification, fraud adjudication, or a replacement for the upstream system of record. + +## Related Documentation + +- [docs/README.md](/Users/christopher/Projects/trustsignal/docs/README.md) +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/wiki/Quick-Verification-Example.md b/wiki/Quick-Verification-Example.md index 91d58ef..468ddc9 100644 --- a/wiki/Quick-Verification-Example.md +++ b/wiki/Quick-Verification-Example.md @@ -1,26 +1,44 @@ **Navigation** -- [Home](Home) -- [What is TrustSignal](What-is-TrustSignal) -- [Architecture](Evidence-Integrity-Architecture) -- [Verification Receipts](Verification-Receipts) -- [API Overview](API-Overview) -- [Claims Boundary](Claims-Boundary) -- [Quick Verification Example](Quick-Verification-Example) -- [Vanta Integration Example](Vanta-Integration-Example) +- [Home](Home.md) +- [What is TrustSignal](What-is-TrustSignal.md) +- [Architecture](Evidence-Integrity-Architecture.md) +- [Verification Receipts](Verification-Receipts.md) +- [API Overview](API-Overview.md) +- [Claims Boundary](Claims-Boundary.md) +- [Quick Verification Example](Quick-Verification-Example.md) +- [Vanta Integration Example](Vanta-Integration-Example.md) # Quick Verification Example -## Problem +Short description: +This page walks through a minimal TrustSignal evaluator flow for verification signals, signed verification receipts, verifiable provenance, and later verification. + +Audience: +- partner evaluators +- integration engineers +- developers + +## Problem / Context This example is for partner engineers who want the smallest realistic TrustSignal flow that shows what goes in, what comes back, and how later verification works. It is intended for workflows where tampered evidence, provenance loss, artifact substitution, and stale evidence matter after collection. -## Verification Lifecycle +## Integrity Model The canonical lifecycle diagram is documented in [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md). This example uses the current integration-facing lifecycle to create a verification, return verification signals plus a signed verification receipt, store the receipt with the workflow record, and later verify stored receipt state during audit review. +## How It Works + +This example shows: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- existing workflow integration through the public lifecycle + ## Demo Start here for the full evaluator path: @@ -30,15 +48,20 @@ Start here for the full evaluator path: - [OpenAPI contract](/Users/christopher/Projects/trustsignal/openapi.yaml) - [Postman collection](/Users/christopher/Projects/trustsignal/postman/TrustSignal.postman_collection.json) -## Integration Model +## API And Examples This example is a deliberate evaluator path. It is designed to show the verification lifecycle before production authentication, signing, and environment requirements are fully configured. +## Production Considerations + +> [!IMPORTANT] +> Production considerations: this is a compact evaluator example. Production deployment still requires explicit authentication, signing configuration, infrastructure controls, and operational review. + ## Production Deployment Requirements Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. -## Technical Details +## Example Or Diagram ```mermaid sequenceDiagram @@ -121,16 +144,21 @@ curl -X POST -H "x-api-key: $TRUSTSIGNAL_API_KEY" \ ### Recent Verification Timing -Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:22:06.846Z`: +Recent local benchmark snapshot from [bench/results/latest.md](/Users/christopher/Projects/trustsignal/bench/results/latest.md) at `2026-03-12T22:30:04.260Z`: -- clean verification request latency: mean `5.06 ms`, median `3.78 ms`, p95 `19.57 ms` -- signed receipt generation latency: mean `0.38 ms`, median `0.32 ms`, p95 `0.90 ms` -- receipt lookup latency: mean `0.60 ms`, median `0.57 ms`, p95 `0.71 ms` -- later verification latency: mean `0.76 ms`, median `0.72 ms`, p95 `1.07 ms` -- tampered artifact detection latency: mean `8.02 ms`, median `5.01 ms`, p95 `42.84 ms` +- clean verification request latency: mean `5.24 ms`, median `4.11 ms`, p95 `21.65 ms` +- signed receipt generation latency: mean `0.34 ms`, median `0.32 ms`, p95 `0.63 ms` +- receipt lookup latency: mean `0.57 ms`, median `0.56 ms`, p95 `0.63 ms` +- later verification latency: mean `0.77 ms`, median `0.71 ms`, p95 `1.08 ms` +- tampered artifact detection latency: mean `7.76 ms`, median `5.13 ms`, p95 `42.82 ms` This is a recent local evaluator benchmark snapshot, not a production guarantee. The tampered path is most useful as a behavior check for mismatch handling rather than a parser-completeness claim. +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this example documents the public evaluation flow only. It does not expose proof internals, circuit identifiers, model outputs, signing infrastructure specifics, or internal service topology. + ### What This Does Not Expose This public example does not expose: @@ -142,3 +170,10 @@ This public example does not expose: - internal service topology - witness or prover details - registry scoring algorithms + +## Related Documentation + +- [docs/partner-eval/try-the-api.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/try-the-api.md) +- [docs/partner-eval/benchmark-summary.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/wiki/What-is-TrustSignal.md b/wiki/What-is-TrustSignal.md index 583fa0e..619e861 100644 --- a/wiki/What-is-TrustSignal.md +++ b/wiki/What-is-TrustSignal.md @@ -1,17 +1,25 @@ **Navigation** -- [Home](Home) -- [What is TrustSignal](What-is-TrustSignal) -- [Architecture](Evidence-Integrity-Architecture) -- [Verification Receipts](Verification-Receipts) -- [API Overview](API-Overview) -- [Claims Boundary](Claims-Boundary) -- [Quick Verification Example](Quick-Verification-Example) -- [Vanta Integration Example](Vanta-Integration-Example) +- [Home](Home.md) +- [What is TrustSignal](What-is-TrustSignal.md) +- [Architecture](Evidence-Integrity-Architecture.md) +- [Verification Receipts](Verification-Receipts.md) +- [API Overview](API-Overview.md) +- [Claims Boundary](Claims-Boundary.md) +- [Quick Verification Example](Quick-Verification-Example.md) +- [Vanta Integration Example](Vanta-Integration-Example.md) # What Is TrustSignal -## Problem +Short description: +This page defines TrustSignal as evidence integrity infrastructure and explains how the integrity layer fits into existing workflow integration. + +Audience: +- evaluators +- partner reviewers +- developers + +## Problem / Context Many workflow systems can show that an artifact was collected or reviewed. Fewer can later verify that the same artifact is still the one tied to the recorded decision. In high-stakes workflows, that creates attack surfaces around evidence tampering after collection, artifact substitution attacks, provenance loss in compliance workflows, stale evidence during audit review, and unverifiable documentation chains. @@ -21,12 +29,32 @@ High-loss environments create incentives for those attack paths because the chal TrustSignal is evidence integrity infrastructure. It provides signed verification receipts, verification signals, verifiable provenance metadata, and later verification for existing workflows. +## How It Works + +TrustSignal provides: + +- signed verification receipts +- verification signals +- verifiable provenance +- later verification +- an integrity layer for existing workflow integration + ## Demo The fastest local evaluator path is the 5-minute developer trial: - [5-minute developer trial](/Users/christopher/Projects/trustsignal/demo/README.md) +## Verification Lifecycle + +At a high level, the public verification lifecycle is: + +1. An upstream system submits a verification request. +2. TrustSignal evaluates the request against configured checks. +3. TrustSignal returns verification signals and a signed verification receipt. +4. Downstream systems store the receipt with the workflow record. +5. Later verification confirms receipt integrity, status, and provenance state when needed. + ## Integration The evaluator and demo path in this repository is a deliberate evaluator path. It is designed to show the verification lifecycle safely before production integration requirements are fully configured. @@ -46,17 +74,19 @@ The upstream platform remains the system of record. TrustSignal adds an integrit Local development defaults are intentionally constrained and fail closed where production trust assumptions are not satisfied. Production deployment requires explicit authentication, signing configuration, and environment setup. -## Technical Details +## API And Examples -At a high level, the public verification lifecycle is: +In the current codebase, the integration-facing `/api/v1/*` routes implement that lifecycle. The legacy `/v1/*` surface remains present for the current SDK. -1. An upstream system submits a verification request. -2. TrustSignal evaluates the request against configured checks. -3. TrustSignal returns verification signals and a signed verification receipt. -4. Downstream systems store the receipt with the workflow record. -5. Later verification confirms receipt integrity, status, and provenance state when needed. +## Production Considerations -In the current codebase, the integration-facing `/api/v1/*` routes implement that lifecycle. The legacy `/v1/*` surface remains present for the current SDK. +> [!IMPORTANT] +> Production considerations: evaluator and demo materials show the TrustSignal integrity layer clearly, but production deployment still requires explicit authentication, signing configuration, infrastructure controls, and operational review. + +## Security And Claims Boundary + +> [!NOTE] +> Claims boundary: this page explains the public product position. It does not expose proof internals, signer infrastructure specifics, internal topology, or unsupported legal/compliance claims. ## What TrustSignal Is Not @@ -67,3 +97,10 @@ TrustSignal is not: - a compliance certification service - a fraud adjudication service - a substitute for environment-specific security evidence + +## Related Documentation + +- [wiki/API-Overview.md](/Users/christopher/Projects/trustsignal/wiki/API-Overview.md) +- [docs/verification-lifecycle.md](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [docs/partner-eval/overview.md](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) +- [wiki/Claims-Boundary.md](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) From 6166e61071c9ec1f8911586252b3f65b044f9061 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 19:18:54 -0500 Subject: [PATCH 11/25] ci: add security workflows for scanning and workflow hardening --- .../workflows/security-dependency-review.yml | 17 +++ .github/workflows/security-scorecard.yml | 44 +++++++ .github/workflows/security-trivy.yml | 54 +++++++++ .github/workflows/security-zizmor.yml | 42 +++++++ docs/security-workflows.md | 107 ++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 .github/workflows/security-dependency-review.yml create mode 100644 .github/workflows/security-scorecard.yml create mode 100644 .github/workflows/security-trivy.yml create mode 100644 .github/workflows/security-zizmor.yml create mode 100644 docs/security-workflows.md diff --git a/.github/workflows/security-dependency-review.yml b/.github/workflows/security-dependency-review.yml new file mode 100644 index 0000000..84ac263 --- /dev/null +++ b/.github/workflows/security-dependency-review.yml @@ -0,0 +1,17 @@ +name: Security / Dependency Review + +on: + pull_request: + +permissions: + contents: read + +jobs: + dependency-review: + name: Dependency diff review + runs-on: ubuntu-latest + steps: + - name: Dependency review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high diff --git a/.github/workflows/security-scorecard.yml b/.github/workflows/security-scorecard.yml new file mode 100644 index 0000000..6c62281 --- /dev/null +++ b/.github/workflows/security-scorecard.yml @@ -0,0 +1,44 @@ +name: Security / OpenSSF Scorecard + +on: + push: + branches: + - main + schedule: + - cron: "23 4 * * 1" + +permissions: {} + +jobs: + scorecard: + name: OpenSSF Scorecard analysis + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + security-events: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.1.7 + with: + persist-credentials: false + + - name: Run OpenSSF Scorecard + uses: ossf/scorecard-action@v2.4.3 + with: + results_file: scorecard-results.sarif + results_format: sarif + publish_results: true + + - name: Upload Scorecard SARIF + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: scorecard-results.sarif + + - name: Upload Scorecard artifact + uses: actions/upload-artifact@v4 + with: + name: scorecard-results + path: scorecard-results.sarif + retention-days: 7 diff --git a/.github/workflows/security-trivy.yml b/.github/workflows/security-trivy.yml new file mode 100644 index 0000000..9c8356c --- /dev/null +++ b/.github/workflows/security-trivy.yml @@ -0,0 +1,54 @@ +name: Security / Trivy Filesystem Scan + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + security-events: write + +jobs: + trivy-fs: + name: Trivy repository scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.1.7 + with: + persist-credentials: false + + - name: Run Trivy filesystem scan + uses: aquasecurity/trivy-action@0.34.0 + with: + scan-type: fs + scan-ref: . + scanners: vuln + vuln-type: os,library + severity: HIGH,CRITICAL + ignore-unfixed: true + hide-progress: true + format: sarif + output: trivy-results.sarif + limit-severities-for-sarif: true + exit-code: "0" + skip-dirs: node_modules,.next,dist,coverage,.git + + - name: Upload Trivy SARIF + if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: trivy-results.sarif + + - name: Summarize Trivy mode + if: always() + run: | + { + echo "## Trivy filesystem scan" + echo "" + echo "- Mode: advisory" + echo "- Scope: filesystem vulnerability scan with HIGH/CRITICAL severity only" + echo "- Notes: ignores unfixed issues and uploads SARIF for review in Security/code scanning when token permissions allow it" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/security-zizmor.yml b/.github/workflows/security-zizmor.yml new file mode 100644 index 0000000..9b156b5 --- /dev/null +++ b/.github/workflows/security-zizmor.yml @@ -0,0 +1,42 @@ +name: Security / zizmor Workflow Audit + +on: + pull_request: + paths: + - ".github/workflows/**" + push: + paths: + - ".github/workflows/**" + +permissions: {} + +jobs: + zizmor: + name: zizmor advisory audit + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + steps: + - name: Checkout + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.1.7 + with: + persist-credentials: false + + - name: Run zizmor in advisory mode + continue-on-error: true + uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0 + with: + advanced-security: false + annotations: true + + - name: Summarize zizmor mode + if: always() + run: | + { + echo "## zizmor workflow audit" + echo "" + echo "- Mode: advisory" + echo "- Trigger: only when files under .github/workflows/** change" + echo "- Findings: review annotations and job logs; the workflow does not fail the check solely for findings at this stage" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/security-workflows.md b/docs/security-workflows.md new file mode 100644 index 0000000..15680fe --- /dev/null +++ b/docs/security-workflows.md @@ -0,0 +1,107 @@ +# TrustSignal Security Workflows + +> TrustSignal uses a minimal set of security-focused GitHub Actions workflows to scan the repository, review dependency changes, and harden workflow configuration over time. + +## Overview + +These workflows are intended to add practical security coverage without introducing broad permissions or noisy initial rollout behavior. + +## Trivy Filesystem Scan + +Workflow: +- `.github/workflows/security-trivy.yml` + +What it does: +- scans the repository filesystem with Trivy +- focuses on `HIGH` and `CRITICAL` vulnerabilities +- ignores unfixed issues to reduce early noise +- uploads SARIF results for review + +When it runs: +- on every pull request +- on pushes to `main` + +How to interpret failures: +- this workflow starts in advisory mode and does not fail the job on findings +- review SARIF/code scanning results for actionable `HIGH` or `CRITICAL` issues +- on forked pull requests, SARIF upload may be skipped because GitHub does not grant `security-events: write` to untrusted fork tokens + +Mode: +- advisory + +## OpenSSF Scorecard + +Workflow: +- `.github/workflows/security-scorecard.yml` + +What it does: +- runs OpenSSF Scorecard against the repository +- uploads SARIF results +- publishes results through the standard Scorecard-supported path + +When it runs: +- on pushes to `main` +- weekly on schedule + +How to interpret failures: +- failures usually indicate workflow/configuration issues, permissions issues, or a Scorecard execution problem +- review the SARIF upload and workflow logs first + +Mode: +- advisory by default unless later enforced through branch protection or policy + +## zizmor Workflow Audit + +Workflow: +- `.github/workflows/security-zizmor.yml` + +What it does: +- audits GitHub Actions workflows for common security issues +- emits annotations and log findings + +When it runs: +- only when files under `.github/workflows/**` change + +How to interpret failures: +- the workflow is intentionally advisory and uses `continue-on-error` +- findings should still be reviewed and fixed, but they do not block merges at this stage + +Mode: +- advisory + +## Dependency Review + +Workflow: +- `.github/workflows/security-dependency-review.yml` + +What it does: +- reviews dependency diffs on pull requests +- fails if a pull request introduces `high` or `critical` vulnerabilities through dependency changes + +When it runs: +- on pull requests + +Support note: +- GitHub Dependency Review is supported for public repositories and for private repositories with GitHub Advanced Security + +How to interpret failures: +- a failing result means the dependency diff introduced vulnerable dependencies at or above the configured threshold +- review the dependency review summary in the workflow run before merging + +Mode: +- blocking + +## Permission Model + +These workflows follow a least-privilege approach: + +- `contents: read` is used where repository checkout or metadata access is required +- `security-events: write` is granted only to SARIF-publishing workflows +- `id-token: write` is granted only to Scorecard because publishing results requires it +- no workflow uses broad write permissions or repository secrets + +## Operational Guidance + +- Treat Trivy and zizmor as early-warning signals during rollout. +- Treat Dependency Review as the primary blocking dependency-diff control. +- Review Scorecard results over time for repository hardening trends rather than expecting every check to be perfect immediately. From 6e5154fceaaa4e30f8a0f158aef5178d8e067d31 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 19:28:01 -0500 Subject: [PATCH 12/25] ci: add GitHub security workflows, Dependabot, and settings checklist --- .github/dependabot.yml | 34 +++++ .github/pull_request_template.md | 33 ++++- ...dency-review.yml => dependency-review.yml} | 2 +- .../{security-scorecard.yml => scorecard.yml} | 8 +- .../{security-trivy.yml => trivy.yml} | 2 +- .../{security-zizmor.yml => zizmor.yml} | 8 +- README.md | 2 + docs/README.md | 2 + docs/github-settings-checklist.md | 108 ++++++++++++++ docs/security-workflows.md | 134 ++++++++++-------- 10 files changed, 254 insertions(+), 79 deletions(-) rename .github/workflows/{security-dependency-review.yml => dependency-review.yml} (66%) rename .github/workflows/{security-scorecard.yml => scorecard.yml} (78%) rename .github/workflows/{security-trivy.yml => trivy.yml} (92%) rename .github/workflows/{security-zizmor.yml => zizmor.yml} (77%) create mode 100644 docs/github-settings-checklist.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 69def6f..356dd9b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,19 +1,53 @@ version: 2 + updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" + day: "monday" + time: "05:00" + timezone: "America/Chicago" open-pull-requests-limit: 5 + groups: + npm-production: + dependency-type: "production" + npm-development: + dependency-type: "development" labels: - "dependencies" - "security" + commit-message: + prefix: "deps" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + day: "monday" + time: "05:30" + timezone: "America/Chicago" open-pull-requests-limit: 5 + groups: + github-actions: + patterns: + - "*" + labels: + - "dependencies" + - "security" + commit-message: + prefix: "deps" + + - package-ecosystem: "cargo" + directory: "/circuits/non_mem_gadget" + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + timezone: "America/Chicago" + open-pull-requests-limit: 3 labels: - "dependencies" - "security" + commit-message: + prefix: "deps" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6dfd96c..1295968 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,33 @@ ## Summary -- Describe the change +- Describe the change and why it is needed. -## AI Disclosure +## Change Type -- [ ] AI-assisted changes are included in this PR +- [ ] Runtime or API behavior +- [ ] Security or repo governance +- [ ] Workflow or CI configuration +- [ ] Documentation or claims boundary only +- [ ] Dependency update -## Review Checklist +## Security Review -- [ ] Human review requested -- [ ] Tests added or updated where appropriate +- [ ] Security impact is described - [ ] No secrets, tokens, cookies, or raw PII were added to code, logs, fixtures, or docs -- [ ] Security impact and remaining risks are described +- [ ] New permissions, auth assumptions, or trust-boundary changes are called out +- [ ] Dependency changes were reviewed for risk + +## Claims Boundary And Docs + +- [ ] Public-facing claims or evaluator docs were updated if needed +- [ ] No unsupported claims were introduced + +## Validation + +- [ ] Human review requested +- [ ] Tests or validation commands were run where appropriate +- [ ] Workflow changes were reviewed for least privilege and pinned actions + +## AI Disclosure + +- [ ] AI-assisted changes are included in this PR diff --git a/.github/workflows/security-dependency-review.yml b/.github/workflows/dependency-review.yml similarity index 66% rename from .github/workflows/security-dependency-review.yml rename to .github/workflows/dependency-review.yml index 84ac263..f52db3f 100644 --- a/.github/workflows/security-dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -12,6 +12,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Dependency review - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v4 # GitHub-maintained action pinned to supported major; Dependabot tracks updates. with: fail-on-severity: high diff --git a/.github/workflows/security-scorecard.yml b/.github/workflows/scorecard.yml similarity index 78% rename from .github/workflows/security-scorecard.yml rename to .github/workflows/scorecard.yml index 6c62281..dff814d 100644 --- a/.github/workflows/security-scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -14,10 +14,10 @@ jobs: name: OpenSSF Scorecard analysis runs-on: ubuntu-latest permissions: - contents: read actions: read - security-events: write + contents: read id-token: write + security-events: write steps: - name: Checkout uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.1.7 @@ -32,12 +32,12 @@ jobs: publish_results: true - name: Upload Scorecard SARIF - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 # GitHub-maintained action pinned to supported major; Dependabot tracks updates. with: sarif_file: scorecard-results.sarif - name: Upload Scorecard artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v4 # GitHub-maintained action pinned to supported major; Dependabot tracks updates. with: name: scorecard-results path: scorecard-results.sarif diff --git a/.github/workflows/security-trivy.yml b/.github/workflows/trivy.yml similarity index 92% rename from .github/workflows/security-trivy.yml rename to .github/workflows/trivy.yml index 9c8356c..26e6709 100644 --- a/.github/workflows/security-trivy.yml +++ b/.github/workflows/trivy.yml @@ -38,7 +38,7 @@ jobs: - name: Upload Trivy SARIF if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 # GitHub-maintained action pinned to supported major; Dependabot tracks updates. with: sarif_file: trivy-results.sarif diff --git a/.github/workflows/security-zizmor.yml b/.github/workflows/zizmor.yml similarity index 77% rename from .github/workflows/security-zizmor.yml rename to .github/workflows/zizmor.yml index 9b156b5..10676f0 100644 --- a/.github/workflows/security-zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -15,15 +15,15 @@ jobs: name: zizmor advisory audit runs-on: ubuntu-latest permissions: - contents: read actions: read + contents: read steps: - name: Checkout uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v4.1.7 with: persist-credentials: false - - name: Run zizmor in advisory mode + - name: Run zizmor continue-on-error: true uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0 with: @@ -37,6 +37,6 @@ jobs: echo "## zizmor workflow audit" echo "" echo "- Mode: advisory" - echo "- Trigger: only when files under .github/workflows/** change" - echo "- Findings: review annotations and job logs; the workflow does not fail the check solely for findings at this stage" + echo "- Scope: GitHub Actions workflow security linting" + echo "- Notes: findings are annotated in the run and should be reviewed before merging workflow changes" } >> "$GITHUB_STEP_SUMMARY" diff --git a/README.md b/README.md index 4392d11..db58378 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Audience: - [Documentation index](/Users/christopher/Projects/trustsignal/docs/README.md) - [Partner evaluation overview](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) - [Verification lifecycle](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) +- [Security workflows](/Users/christopher/Projects/trustsignal/docs/security-workflows.md) +- [GitHub settings checklist](/Users/christopher/Projects/trustsignal/docs/github-settings-checklist.md) - [API overview](/Users/christopher/Projects/trustsignal/wiki/API-Overview.md) - [Claims boundary](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) diff --git a/docs/README.md b/docs/README.md index d0c51d3..aac53d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,8 @@ Audience: - [Partner evaluation overview](/Users/christopher/Projects/trustsignal/docs/partner-eval/overview.md) - [Verification lifecycle](/Users/christopher/Projects/trustsignal/docs/verification-lifecycle.md) - [Security summary](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [Security workflows](/Users/christopher/Projects/trustsignal/docs/security-workflows.md) +- [GitHub settings checklist](/Users/christopher/Projects/trustsignal/docs/github-settings-checklist.md) - [Benchmark summary](/Users/christopher/Projects/trustsignal/docs/partner-eval/benchmark-summary.md) - [Claims boundary](/Users/christopher/Projects/trustsignal/wiki/Claims-Boundary.md) - [Docs architecture](/Users/christopher/Projects/trustsignal/docs/templates/docs-architecture.md) diff --git a/docs/github-settings-checklist.md b/docs/github-settings-checklist.md new file mode 100644 index 0000000..652ac60 --- /dev/null +++ b/docs/github-settings-checklist.md @@ -0,0 +1,108 @@ +# TrustSignal GitHub Settings Checklist + +> Codex can add repository files and workflows, but it cannot safely click or verify GitHub repository settings from inside the repo. After this PR lands, verify the settings below in GitHub. + +Short description: +This checklist separates what TrustSignal now manages in-repo from the GitHub settings that still require manual verification in the repository UI. + +Audience: +- repository administrators +- security reviewers +- engineering leads + +## In-Repo Automation + +The repository now manages these controls in code: + +- Dependabot configuration in `.github/dependabot.yml` +- dependency diff review in `.github/workflows/dependency-review.yml` +- repository vulnerability scanning in `.github/workflows/trivy.yml` +- workflow hardening review in `.github/workflows/zizmor.yml` +- weekly and push-based repository score tracking in `.github/workflows/scorecard.yml` +- review hygiene defaults in `.github/pull_request_template.md` + +Not yet managed in-repo: + +- `CODEOWNERS`, because repository-specific GitHub usernames or team slugs should not be guessed + +## Manual GitHub Settings Still Required + +### 1. Actions + +Verify in GitHub: +- GitHub Actions is enabled for the repository +- workflow permissions remain restricted to the default least-privilege mode unless a specific workflow requires more +- branch and environment secrets are reviewed for necessity and rotated if stale + +### 2. Dependency Graph And Dependabot + +Verify in GitHub: +- Dependency graph is enabled +- Dependabot alerts are enabled +- Dependabot security updates are enabled if supported by the repository plan +- Dependabot version updates are allowed for this repository + +### 3. Secret Scanning + +Verify in GitHub: +- secret scanning is enabled if the repository type and plan support it +- push protection is enabled if available and acceptable for the team workflow + +Note: +- secret scanning availability depends on repository visibility and GitHub plan + +### 4. Code Scanning / CodeQL + +Recommended manual setup: +- enable code scanning in GitHub Security +- prefer GitHub CodeQL default setup unless you have a clear reason to maintain advanced CodeQL workflow YAML in-repo + +Reason: +- this repo already uploads third-party SARIF from Trivy and Scorecard +- CodeQL default setup is usually the safer and lower-maintenance starting point for JavaScript/TypeScript repositories + +### 5. Branch Protection Or Rulesets + +Configure branch protection or a repository ruleset for `main`: + +- require pull requests before merge +- require at least one human PR review +- dismiss stale approvals when new commits are pushed if that matches team policy +- disable force pushes to `main` +- restrict direct pushes to `main` +- optionally require branches to be up to date before merge +- add a real `CODEOWNERS` file later if the repository has stable maintainer usernames or org team slugs + +### 6. Required Status Checks + +After the workflows have run successfully on `main`, consider requiring these checks before merge: + +- `typecheck` +- `web-build` +- `test` +- `signed-receipt-smoke` +- `messaging-check` when docs or web copy changes matter +- `Dependency diff review` + +Optional later: + +- `Trivy repository scan` after the advisory rollout proves low-noise +- `zizmor advisory audit` for workflow-change pull requests if branch rulesets can scope that requirement safely + +Advisory only by default: + +- `OpenSSF Scorecard analysis` + +## What To Verify After Merge + +1. Open the repository `Settings` and `Security` tabs in GitHub. +2. Confirm every workflow appears under Actions and is enabled. +3. Confirm Dependabot is creating update PRs on the expected schedule. +4. Confirm the Security tab shows dependency graph, Dependabot alerts, and code scanning as enabled where supported. +5. Add the required status checks only after at least one successful run for each target check. + +## Related Documentation + +- [Security workflows](/Users/christopher/Projects/trustsignal/docs/security-workflows.md) +- [Security summary](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [Documentation index](/Users/christopher/Projects/trustsignal/docs/README.md) diff --git a/docs/security-workflows.md b/docs/security-workflows.md index 15680fe..c4ac960 100644 --- a/docs/security-workflows.md +++ b/docs/security-workflows.md @@ -1,107 +1,117 @@ # TrustSignal Security Workflows -> TrustSignal uses a minimal set of security-focused GitHub Actions workflows to scan the repository, review dependency changes, and harden workflow configuration over time. +> TrustSignal manages a minimal set of security-focused GitHub Actions workflows in-repo. These checks improve repository hygiene and visibility, but they do not replace manual GitHub settings that must still be enabled by a repository administrator. -## Overview +Short description: +This document explains which security and governance controls are now defined in repository files, when they run, and how to interpret advisory versus blocking outcomes. -These workflows are intended to add practical security coverage without introducing broad permissions or noisy initial rollout behavior. +Audience: +- repository administrators +- security reviewers +- maintainers -## Trivy Filesystem Scan +## In-Repo Automation + +The repository now defines these security workflows: + +- `.github/workflows/dependency-review.yml` +- `.github/workflows/trivy.yml` +- `.github/workflows/scorecard.yml` +- `.github/workflows/zizmor.yml` -Workflow: -- `.github/workflows/security-trivy.yml` +## Dependency Review What it does: -- scans the repository filesystem with Trivy -- focuses on `HIGH` and `CRITICAL` vulnerabilities -- ignores unfixed issues to reduce early noise -- uploads SARIF results for review +- reviews dependency diffs on pull requests +- blocks only when a pull request introduces `high` or `critical` vulnerabilities through dependency changes When it runs: -- on every pull request -- on pushes to `main` - -How to interpret failures: -- this workflow starts in advisory mode and does not fail the job on findings -- review SARIF/code scanning results for actionable `HIGH` or `CRITICAL` issues -- on forked pull requests, SARIF upload may be skipped because GitHub does not grant `security-events: write` to untrusted fork tokens +- on pull requests Mode: -- advisory +- blocking -## OpenSSF Scorecard +How to interpret failures: +- a failing result means the dependency diff introduced a clearly risky dependency update +- review the dependency review summary in the GitHub workflow run before merging -Workflow: -- `.github/workflows/security-scorecard.yml` +## Trivy Filesystem Scan What it does: -- runs OpenSSF Scorecard against the repository -- uploads SARIF results -- publishes results through the standard Scorecard-supported path +- scans the repository filesystem for `HIGH` and `CRITICAL` vulnerabilities +- ignores unfixed issues in the first rollout to reduce noise +- uploads SARIF results when GitHub token permissions allow it When it runs: +- on every pull request - on pushes to `main` -- weekly on schedule - -How to interpret failures: -- failures usually indicate workflow/configuration issues, permissions issues, or a Scorecard execution problem -- review the SARIF upload and workflow logs first Mode: -- advisory by default unless later enforced through branch protection or policy +- advisory -## zizmor Workflow Audit +How to interpret failures: +- this workflow currently does not fail the job on findings +- review SARIF/code scanning results for actionable issues +- on forked pull requests, SARIF upload may be skipped because GitHub does not grant `security-events: write` to untrusted fork tokens -Workflow: -- `.github/workflows/security-zizmor.yml` +## OpenSSF Scorecard What it does: -- audits GitHub Actions workflows for common security issues -- emits annotations and log findings +- runs OpenSSF Scorecard against the repository +- uploads SARIF results and stores the SARIF file as an artifact +- publishes Scorecard results through the supported Scorecard path When it runs: -- only when files under `.github/workflows/**` change - -How to interpret failures: -- the workflow is intentionally advisory and uses `continue-on-error` -- findings should still be reviewed and fixed, but they do not block merges at this stage +- on pushes to `main` +- weekly on schedule Mode: - advisory -## Dependency Review +How to interpret failures: +- failures usually indicate a workflow/configuration issue, a permissions problem, or a Scorecard execution issue +- review the workflow logs and SARIF upload details first -Workflow: -- `.github/workflows/security-dependency-review.yml` +## zizmor Workflow Audit What it does: -- reviews dependency diffs on pull requests -- fails if a pull request introduces `high` or `critical` vulnerabilities through dependency changes +- audits GitHub Actions workflows for common workflow security issues +- emits annotations and logs for maintainers reviewing workflow changes When it runs: -- on pull requests +- only when files in `.github/workflows/**` change -Support note: -- GitHub Dependency Review is supported for public repositories and for private repositories with GitHub Advanced Security +Mode: +- advisory How to interpret failures: -- a failing result means the dependency diff introduced vulnerable dependencies at or above the configured threshold -- review the dependency review summary in the workflow run before merging +- findings are intentionally non-blocking during the rollout period +- maintainers should still review and address findings before merging workflow changes -Mode: -- blocking +## Least-Privilege Design + +These workflows follow a least-privilege model: + +- `contents: read` is used where checkout or repository metadata access is required +- `security-events: write` is granted only to SARIF-uploading workflows +- `id-token: write` is granted only to Scorecard because its standard publishing flow requires it +- no workflow uses `pull_request_target` +- no workflow exposes repository secrets unnecessarily + +## What Is Not Controlled By Repo Files -## Permission Model +These workflows do not automatically configure repository settings such as: -These workflows follow a least-privilege approach: +- enabling Dependency Graph +- enabling Dependabot alerts or security updates +- enabling secret scanning +- enabling CodeQL or GitHub code scanning defaults +- configuring branch protection or rulesets -- `contents: read` is used where repository checkout or metadata access is required -- `security-events: write` is granted only to SARIF-publishing workflows -- `id-token: write` is granted only to Scorecard because publishing results requires it -- no workflow uses broad write permissions or repository secrets +Those controls still require manual verification in GitHub after merge. -## Operational Guidance +## Related Documentation -- Treat Trivy and zizmor as early-warning signals during rollout. -- Treat Dependency Review as the primary blocking dependency-diff control. -- Review Scorecard results over time for repository hardening trends rather than expecting every check to be perfect immediately. +- [GitHub settings checklist](/Users/christopher/Projects/trustsignal/docs/github-settings-checklist.md) +- [Security summary](/Users/christopher/Projects/trustsignal/docs/security-summary.md) +- [Documentation index](/Users/christopher/Projects/trustsignal/docs/README.md) From 14f7fdbbd5f29d4b44b94b9932e7ad7e12e77383 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 19:32:48 -0500 Subject: [PATCH 13/25] compliance: add SOC2 readiness mock audit framework --- README.md | 6 + .../evidence/access-control-evidence.md | 14 ++ .../evidence/ci-security-evidence.md | 14 ++ .../compliance/evidence/logging-monitoring.md | 14 ++ .../evidence/vulnerability-management.md | 14 ++ .../policies/access-control-policy.md | 33 +++ .../policies/data-retention-policy.md | 31 +++ .../policies/incident-response-policy.md | 33 +++ .../policies/secure-development-policy.md | 33 +++ .../compliance/policies/vendor-risk-policy.md | 31 +++ docs/compliance/security-posture.md | 26 +++ docs/compliance/soc2/controls.md | 188 ++++++++++++++++++ docs/compliance/soc2/readiness-checklist.md | 93 +++++++++ docs/compliance/soc2/readiness-report.md | 50 +++++ scripts/security-readiness.ts | 101 ++++++++++ scripts/soc2-readiness.ts | 184 +++++++++++++++++ 16 files changed, 865 insertions(+) create mode 100644 docs/compliance/evidence/access-control-evidence.md create mode 100644 docs/compliance/evidence/ci-security-evidence.md create mode 100644 docs/compliance/evidence/logging-monitoring.md create mode 100644 docs/compliance/evidence/vulnerability-management.md create mode 100644 docs/compliance/policies/access-control-policy.md create mode 100644 docs/compliance/policies/data-retention-policy.md create mode 100644 docs/compliance/policies/incident-response-policy.md create mode 100644 docs/compliance/policies/secure-development-policy.md create mode 100644 docs/compliance/policies/vendor-risk-policy.md create mode 100644 docs/compliance/security-posture.md create mode 100644 docs/compliance/soc2/controls.md create mode 100644 docs/compliance/soc2/readiness-checklist.md create mode 100644 docs/compliance/soc2/readiness-report.md create mode 100644 scripts/security-readiness.ts create mode 100644 scripts/soc2-readiness.ts diff --git a/README.md b/README.md index db58378..c2b3ed3 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,12 @@ The upstream platform remains the system of record. TrustSignal adds an integrit > [!NOTE] > Claims boundary: this repository documents the public integration and evaluation surface only. It does not expose proof internals, circuit identifiers, model outputs, signing infrastructure specifics, or internal service topology. +## Compliance and Security Readiness + +TrustSignal includes a repository-level SOC 2 readiness framework for assessing security posture, documentation maturity, governance evidence, and mock-audit gaps. It is intended to support internal review and partner diligence preparation. It does not claim SOC 2 certification. + +- [SOC 2 readiness report](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/readiness-report.md) + ## Integration Boundary Notes The local evaluator path is intentionally constrained. Local development defaults are a deliberate evaluator and development path, and they fail closed where production trust assumptions are not satisfied. diff --git a/docs/compliance/evidence/access-control-evidence.md b/docs/compliance/evidence/access-control-evidence.md new file mode 100644 index 0000000..fff5e84 --- /dev/null +++ b/docs/compliance/evidence/access-control-evidence.md @@ -0,0 +1,14 @@ +# TrustSignal Access Control Evidence Guide + +This placeholder tracks the types of evidence auditors normally expect for access control testing. + +Expected evidence: +- GitHub organization membership and repository access listings +- branch protection or ruleset configuration for protected branches +- reviewer requirements for pull requests +- access review logs or exported review records +- joiner, mover, and leaver tickets showing approval and removal actions + +TrustSignal notes: +- repository files can document expected controls, but GitHub UI settings and access review outputs must still be collected manually +- attach dated screenshots, exports, or ticket references here during audit preparation diff --git a/docs/compliance/evidence/ci-security-evidence.md b/docs/compliance/evidence/ci-security-evidence.md new file mode 100644 index 0000000..f54c916 --- /dev/null +++ b/docs/compliance/evidence/ci-security-evidence.md @@ -0,0 +1,14 @@ +# TrustSignal CI Security Evidence Guide + +This placeholder tracks evidence for CI-based security controls and secure change management. + +Expected evidence: +- GitHub Actions run history for build, typecheck, and security workflows +- workflow definitions showing least-privilege permissions +- screenshots or exported records of required status checks +- evidence that failing checks block merges where intended +- change history for workflow updates + +TrustSignal notes: +- current repository automation includes dependency review, Trivy, OpenSSF Scorecard, and zizmor +- collect successful and failed run examples to show operation and triage behavior diff --git a/docs/compliance/evidence/logging-monitoring.md b/docs/compliance/evidence/logging-monitoring.md new file mode 100644 index 0000000..5f03de3 --- /dev/null +++ b/docs/compliance/evidence/logging-monitoring.md @@ -0,0 +1,14 @@ +# TrustSignal Logging and Monitoring Evidence Guide + +This placeholder tracks evidence for monitoring, detection, and operational review. + +Expected evidence: +- logging standards or operational runbooks +- examples of security-relevant logs or alerts +- monitoring dashboard screenshots or exported views +- incident escalation records tied to monitored events +- evidence of periodic review for monitoring outputs + +TrustSignal notes: +- repository files can describe expectations, but auditors will typically expect operational evidence from deployed systems and review processes +- document where logs are retained, who reviews them, and how anomalies are escalated diff --git a/docs/compliance/evidence/vulnerability-management.md b/docs/compliance/evidence/vulnerability-management.md new file mode 100644 index 0000000..3c33980 --- /dev/null +++ b/docs/compliance/evidence/vulnerability-management.md @@ -0,0 +1,14 @@ +# TrustSignal Vulnerability Management Evidence Guide + +This placeholder tracks evidence for identifying, reviewing, and remediating vulnerabilities. + +Expected evidence: +- dependency review results on pull requests +- Dependabot alerts or update pull requests +- Trivy scan results +- issue tracking or remediation tickets for material findings +- review notes showing severity-based triage decisions + +TrustSignal notes: +- repository automation can surface findings, but remediation ownership and closure evidence must still be collected manually +- include examples of accepted risk, deferred remediation, and completed fixes where applicable diff --git a/docs/compliance/policies/access-control-policy.md b/docs/compliance/policies/access-control-policy.md new file mode 100644 index 0000000..73b9e48 --- /dev/null +++ b/docs/compliance/policies/access-control-policy.md @@ -0,0 +1,33 @@ +# TrustSignal Access Control Policy + +## Purpose + +Define how TrustSignal grants, reviews, and removes access to code, infrastructure, environments, and sensitive operational tooling. + +## Scope + +This policy applies to employees, contractors, service accounts, and third parties with access to TrustSignal-controlled systems, repositories, or security-relevant data. + +## Responsibilities + +- Engineering leadership approves role definitions and privileged access expectations. +- System owners approve access based on least privilege and business need. +- Administrators implement approved access changes and preserve evidence. +- Personnel with access protect credentials and report suspected misuse promptly. + +## Control Procedures + +1. Access is granted only after documented approval from an authorized owner. +2. Privileged access is limited to personnel with a demonstrated operational need. +3. Shared credentials are prohibited except where a managed service requires a documented break-glass account. +4. Access changes for joiners, movers, and leavers are completed within a defined operating window. +5. Access reviews are performed on a recurring basis and exceptions are tracked to remediation. +6. Repository, CI, and administrative settings should require strong authentication and human review for sensitive changes. + +## Evidence + +- access request and approval records +- periodic access review logs +- repository membership or team export +- branch protection or ruleset screenshots +- deprovisioning records for departed personnel diff --git a/docs/compliance/policies/data-retention-policy.md b/docs/compliance/policies/data-retention-policy.md new file mode 100644 index 0000000..94924ad --- /dev/null +++ b/docs/compliance/policies/data-retention-policy.md @@ -0,0 +1,31 @@ +# TrustSignal Data Retention Policy + +## Purpose + +Define retention, review, and disposal expectations for TrustSignal operational records, compliance evidence, and security-relevant data. + +## Scope + +This policy applies to repository evidence, audit artifacts, logs, incident records, CI outputs, and other retained materials used to support operations or compliance readiness. + +## Responsibilities + +- Data owners classify retained materials and define retention expectations. +- System owners ensure storage locations support controlled access and orderly disposal. +- Compliance owners coordinate evidence preservation for audits, incidents, or partner reviews. + +## Control Procedures + +1. Records are retained only for a documented business, legal, security, or compliance purpose. +2. Retention periods are defined by record category and reviewed periodically. +3. Disposal methods must be appropriate for the sensitivity of the retained material. +4. Security and audit evidence should remain accessible for the agreed review window. +5. Logs and artifacts should avoid raw secrets and unnecessary personal data. + +## Evidence + +- retention schedule or matrix +- evidence inventory +- storage access reviews +- deletion or archival procedures +- examples of retained audit records diff --git a/docs/compliance/policies/incident-response-policy.md b/docs/compliance/policies/incident-response-policy.md new file mode 100644 index 0000000..b2537d3 --- /dev/null +++ b/docs/compliance/policies/incident-response-policy.md @@ -0,0 +1,33 @@ +# TrustSignal Incident Response Policy + +## Purpose + +Define a repeatable process for identifying, escalating, containing, investigating, and recovering from security incidents affecting TrustSignal systems or evidence integrity infrastructure. + +## Scope + +This policy applies to suspected or confirmed incidents involving TrustSignal code, CI/CD systems, repositories, environments, vendors, or security-relevant data. + +## Responsibilities + +- Incident commander coordinates triage, containment, and communication. +- Engineering responders investigate technical scope, impact, and recovery actions. +- Leadership approves external communications when required. +- Security or compliance owners preserve evidence and document lessons learned. + +## Control Procedures + +1. Incidents are classified by severity based on impact to confidentiality, integrity, availability, and trust in verification signals or signed verification receipts. +2. Responders open a tracked incident record and preserve key evidence. +3. Containment actions are documented and approved according to severity and urgency. +4. Recovery steps are validated before normal operations resume. +5. Post-incident review identifies root cause, remediation owners, and control improvements. +6. Critical incidents trigger leadership notification and documented customer or partner communication review where applicable. + +## Evidence + +- incident tickets or case records +- timelines and escalation logs +- forensic or log preservation notes +- post-incident review documents +- remediation tracking records diff --git a/docs/compliance/policies/secure-development-policy.md b/docs/compliance/policies/secure-development-policy.md new file mode 100644 index 0000000..54ac1ea --- /dev/null +++ b/docs/compliance/policies/secure-development-policy.md @@ -0,0 +1,33 @@ +# TrustSignal Secure Development Policy + +## Purpose + +Establish secure software development expectations for TrustSignal so code changes are reviewed, tested, and released with documented security considerations. + +## Scope + +This policy applies to application code, infrastructure-as-code, CI/CD workflows, dependency changes, scripts, and public-facing documentation that could affect security posture or trust claims. + +## Responsibilities + +- Engineers follow secure coding standards and document security-relevant assumptions. +- Reviewers assess security impact, dependency risk, and claims-boundary implications. +- Maintainers ensure CI validation remains effective and least-privilege automation is preserved. +- Leadership prioritizes remediation of material security findings. + +## Control Procedures + +1. Changes are introduced through reviewable pull requests unless a documented emergency process applies. +2. Security-sensitive changes receive explicit reviewer attention for auth, secrets, logging, dependency, and workflow risks. +3. Build and typecheck validation should pass before merge for changes affecting shipping code. +4. Dependency updates are reviewed with automated tooling where available. +5. Secrets, tokens, private keys, and raw PII must not be committed to the repository. +6. Documentation must not overstate security, compliance, or production guarantees beyond available evidence. + +## Evidence + +- pull request records and review comments +- CI build and validation history +- dependency review outputs +- security workflow logs +- remediation tickets for identified issues diff --git a/docs/compliance/policies/vendor-risk-policy.md b/docs/compliance/policies/vendor-risk-policy.md new file mode 100644 index 0000000..e28c46f --- /dev/null +++ b/docs/compliance/policies/vendor-risk-policy.md @@ -0,0 +1,31 @@ +# TrustSignal Vendor Risk Policy + +## Purpose + +Define how TrustSignal evaluates and monitors third-party vendors, hosted services, and material software dependencies that may affect security posture or service integrity. + +## Scope + +This policy applies to cloud providers, development tooling vendors, security tooling vendors, and other third parties that process, store, or materially influence TrustSignal systems or evidence. + +## Responsibilities + +- Business and technical owners identify vendors and classify risk. +- Reviewers assess vendor security posture before onboarding or material expansion. +- System owners track renewal, reassessment, and remediation actions. + +## Control Procedures + +1. Vendors are inventoried and assigned a risk tier based on data sensitivity, operational dependency, and security impact. +2. Higher-risk vendors require documented security review before onboarding. +3. Contractual, privacy, and operational expectations are reviewed where applicable. +4. Vendor reassessment occurs periodically or after material service changes. +5. Critical open risks are tracked with owners and target remediation dates. + +## Evidence + +- vendor inventory +- risk review worksheets +- security questionnaire responses or attestations +- renewal and reassessment notes +- remediation tracking for vendor findings diff --git a/docs/compliance/security-posture.md b/docs/compliance/security-posture.md new file mode 100644 index 0000000..065f3e4 --- /dev/null +++ b/docs/compliance/security-posture.md @@ -0,0 +1,26 @@ +# TrustSignal Security Posture Snapshot + +Generated: 2026-03-13T00:32:09.914Z + +> This report summarizes repository-visible security governance indicators. It is a posture snapshot, not proof that all related GitHub or infrastructure settings are enabled in production. + +## Checks + +| Check | Status | Details | +| --- | --- | --- | +| GitHub workflows present | present | Dependency Review, Trivy, Scorecard, and zizmor workflow files exist. | +| Dependency scanning enabled | present | Dependabot configuration and dependency review workflow are present in-repo. | +| Branch protection indicators | partial | Repository contains documentation or helper automation for branch protection, but actual GitHub rules must be verified manually. | +| CI security tools present | present | Repository-level CI security tooling is present for vulnerabilities, Scorecard, and workflow linting. | + +## Interpretation + +- `present` means the expected repository file or automation exists. +- `partial` means repository indicators exist, but the control still depends on manual GitHub or infrastructure verification. +- `missing` means the repository does not currently provide the expected indicator. + +## Manual Follow-Up + +- Verify branch protection or rulesets directly in GitHub. +- Verify Dependency Graph, Dependabot alerts, and code scanning are enabled in repository settings. +- Capture dated screenshots or exports if the result will be used as audit evidence. diff --git a/docs/compliance/soc2/controls.md b/docs/compliance/soc2/controls.md new file mode 100644 index 0000000..3d8a17a --- /dev/null +++ b/docs/compliance/soc2/controls.md @@ -0,0 +1,188 @@ +# TrustSignal SOC 2 Security Controls Mapping + +> TrustSignal maintains this document as a readiness-oriented control map for the SOC 2 Security Trust Services Criteria. It is intended for internal assessment, partner diligence, and gap remediation planning. It is not a statement of SOC 2 certification. + +Short description: +This document maps observable TrustSignal practices, repository controls, and operating expectations to core SOC 2 Security control areas so teams can identify evidence, confirm coverage, and track remaining gaps. + +Audience: +- engineering leadership +- security reviewers +- compliance coordinators +- partner diligence teams + +## Access Control + +Control objective: +Ensure access to code, infrastructure, secrets, and operational interfaces is provisioned on least-privilege terms and reviewed on a defined cadence. + +Evidence expected by auditors: +- access provisioning and deprovisioning procedures +- role or team-based repository access assignments +- periodic access review records +- authentication and authorization standards +- evidence of restricted production or administrative access + +Example TrustSignal implementation: +- GitHub pull request review requirements and rulesets are documented in [docs/github-settings-checklist.md](/Users/christopher/Projects/trustsignal/docs/github-settings-checklist.md) +- repository guidance emphasizes least-privilege configuration and controlled secrets handling +- access review evidence can be maintained in [docs/compliance/evidence/access-control-evidence.md](/Users/christopher/Projects/trustsignal/docs/compliance/evidence/access-control-evidence.md) + +## Change Management + +Control objective: +Ensure system changes are proposed, reviewed, tested, approved, and traceable before release. + +Evidence expected by auditors: +- pull request templates and reviewer checklists +- branch protection or ruleset evidence +- CI run history for validation checks +- release or deployment approval records +- change log or commit history showing reviewable diffs + +Example TrustSignal implementation: +- `.github/pull_request_template.md` requires security, workflow, and documentation impact review +- GitHub Actions workflows provide CI evidence for build, typecheck, dependency review, and workflow linting +- repository history provides traceability for reviewed changes and targeted control updates + +## Logical Security + +Control objective: +Protect systems and data through authentication, authorization, configuration management, and secure administrative controls. + +Evidence expected by auditors: +- authentication design documentation +- configuration baselines +- environment-variable based secret handling +- system hardening guidance +- evidence of restricted administrative actions + +Example TrustSignal implementation: +- project guardrails require environment variables for secrets and prohibit hardcoded credentials +- CI and documentation emphasize least-privilege permissions for GitHub Actions +- security summaries document trust boundaries and claims boundaries for the public surface + +## System Monitoring + +Control objective: +Detect, log, and review security-relevant events and operational anomalies in a timely manner. + +Evidence expected by auditors: +- logging and monitoring procedures +- alerting or incident escalation definitions +- vulnerability scan records +- CI security scan outputs +- periodic review notes for monitoring outputs + +Example TrustSignal implementation: +- repository workflows run dependency review, Trivy, and OpenSSF Scorecard checks +- monitoring evidence guidance is maintained in [docs/compliance/evidence/logging-monitoring.md](/Users/christopher/Projects/trustsignal/docs/compliance/evidence/logging-monitoring.md) +- security posture snapshots can be generated by `scripts/security-readiness.ts` + +## Incident Response + +Control objective: +Define, communicate, and exercise a repeatable process for identifying, escalating, containing, and recovering from security incidents. + +Evidence expected by auditors: +- formal incident response policy +- severity definitions and escalation roles +- incident ticket or post-incident review examples +- communication templates +- tabletop or exercise records + +Example TrustSignal implementation: +- baseline incident response guidance is defined in [docs/compliance/policies/incident-response-policy.md](/Users/christopher/Projects/trustsignal/docs/compliance/policies/incident-response-policy.md) +- documentation separates technical evidence from policy requirements so response gaps can be tracked explicitly + +## Vendor Management + +Control objective: +Assess and monitor third-party vendors and dependencies that affect security, availability, or processing integrity. + +Evidence expected by auditors: +- vendor inventory +- risk classification criteria +- contract or review checklists +- periodic reassessment records +- dependency monitoring records + +Example TrustSignal implementation: +- dependency review and Dependabot provide ongoing software dependency visibility +- vendor review expectations are documented in [docs/compliance/policies/vendor-risk-policy.md](/Users/christopher/Projects/trustsignal/docs/compliance/policies/vendor-risk-policy.md) +- third-party integration risk can be recorded as part of readiness remediation work + +## Data Protection + +Control objective: +Protect sensitive data through classification, handling, retention, transmission, and disposal controls. + +Evidence expected by auditors: +- data handling standards +- retention and disposal policy +- encryption and secret-handling standards +- sample evidence of data minimization and redaction practices +- system documentation describing protected data paths + +Example TrustSignal implementation: +- repository guardrails prohibit secrets and raw PII in code, logs, and fixtures +- retention guidance is defined in [docs/compliance/policies/data-retention-policy.md](/Users/christopher/Projects/trustsignal/docs/compliance/policies/data-retention-policy.md) +- documentation emphasizes verifiable provenance, signed verification receipts, and later verification without exposing internal proof material + +## Secure Development + +Control objective: +Build and release software using secure engineering practices, code review, dependency management, and vulnerability remediation. + +Evidence expected by auditors: +- secure development standards +- code review requirements +- SAST or dependency scan outputs +- remediation tracking +- developer guidance for secret handling and safe defaults + +Example TrustSignal implementation: +- secure development guidance is documented in [docs/compliance/policies/secure-development-policy.md](/Users/christopher/Projects/trustsignal/docs/compliance/policies/secure-development-policy.md) +- GitHub Actions enforce build and typecheck validation +- dependency review, Trivy, and workflow linting provide repository-level security evidence + +## Risk Assessment + +Control objective: +Identify, assess, prioritize, and track security and operational risks on a recurring basis. + +Evidence expected by auditors: +- risk register or assessment worksheets +- remediation tracking +- periodic review cadence +- security review records for major changes +- evidence of management follow-up for identified gaps + +Example TrustSignal implementation: +- this readiness framework provides repeatable scoring and remediation outputs +- `scripts/soc2-readiness.ts` generates category scores and recommended remediation items +- risk discussions can be anchored to repository evidence, policy gaps, and manual GitHub settings gaps + +## Backup and Recovery + +Control objective: +Ensure critical systems and evidence can be restored within acceptable timeframes after disruption or data loss. + +Evidence expected by auditors: +- backup policy and responsibilities +- recovery procedures +- restoration test evidence +- infrastructure-provider backup settings or reports +- defined recovery objectives where applicable + +Example TrustSignal implementation: +- recovery expectations are included in the readiness checklist and policy set +- operational evidence can be captured separately from product code to support future audit preparation +- manual infrastructure verification remains necessary because repository files alone cannot prove backup execution + +## Related Documentation + +- [SOC 2 readiness checklist](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/readiness-checklist.md) +- [SOC 2 readiness report](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/readiness-report.md) +- [Security posture snapshot](/Users/christopher/Projects/trustsignal/docs/compliance/security-posture.md) +- [GitHub settings checklist](/Users/christopher/Projects/trustsignal/docs/github-settings-checklist.md) diff --git a/docs/compliance/soc2/readiness-checklist.md b/docs/compliance/soc2/readiness-checklist.md new file mode 100644 index 0000000..2a0ab72 --- /dev/null +++ b/docs/compliance/soc2/readiness-checklist.md @@ -0,0 +1,93 @@ +# TrustSignal SOC 2 Security Readiness Checklist + +> This checklist is designed for mock-audit readiness reviews against SOC 2 Security criteria. It helps the team separate implemented controls from partially implemented controls and evidence that still depends on manual operations. + +Short description: +Use this checklist during internal security reviews, partner diligence preparation, and pre-audit cleanup to confirm whether TrustSignal has evidence-ready controls rather than undocumented intent. + +Audience: +- engineering managers +- security leads +- compliance coordinators + +## Access Control + +- [ ] Repository and infrastructure access is granted on least-privilege terms +- [ ] Access reviews are documented at a defined cadence +- [ ] Joiner, mover, and leaver processes are documented +- [ ] Administrative access paths are restricted and logged + +## Change Management + +- [ ] Pull requests are required for protected branches +- [ ] Human review is required before merge +- [ ] Required status checks are configured for mainline changes +- [ ] Emergency change procedures are documented + +## Logical Security + +- [ ] Secrets are stored outside the repository and rotated as needed +- [ ] Environment-specific configuration is documented +- [ ] Public-safe claims boundary is documented +- [ ] Administrative privileges are limited and reviewed + +## System Monitoring + +- [ ] Security-relevant CI scan outputs are retained +- [ ] Logging and monitoring expectations are documented +- [ ] Alert escalation ownership is defined +- [ ] Security findings are reviewed and triaged + +## Incident Response + +- [ ] Incident response policy is approved and current +- [ ] Roles and severity levels are defined +- [ ] Contact and escalation paths are documented +- [ ] Tabletop or post-incident evidence exists + +## Vendor Management + +- [ ] Critical vendors are inventoried +- [ ] Vendor risk review criteria are documented +- [ ] Reassessment cadence is defined +- [ ] Dependency risk is monitored continuously + +## Data Protection + +- [ ] Sensitive-data handling rules are documented +- [ ] Retention and disposal rules are documented +- [ ] Logging avoids raw secrets and PII +- [ ] Encryption requirements are defined where applicable + +## Secure Development + +- [ ] Secure development policy is documented +- [ ] Dependency updates are reviewed +- [ ] CI validation covers build and typecheck +- [ ] Security scans run on repository changes + +## Risk Assessment + +- [ ] A periodic security risk review exists +- [ ] Remediation items are tracked to closure +- [ ] Material system changes trigger risk review +- [ ] Readiness scoring is refreshed on a defined cadence + +## Backup and Recovery + +- [ ] Backup responsibilities are assigned +- [ ] Recovery procedures are documented +- [ ] Restore testing evidence exists +- [ ] Critical evidence repositories are recoverable + +## Evidence Review Notes + +- Repository files and CI workflows provide only partial audit evidence. +- GitHub settings, access reviews, infrastructure controls, and restore testing still require manual evidence collection. +- Generated readiness scores should be treated as internal assessment inputs, not as an audit result. + +## Related Documentation + +- [SOC 2 controls mapping](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/controls.md) +- [SOC 2 readiness report](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/readiness-report.md) +- [Policy templates](/Users/christopher/Projects/trustsignal/docs/compliance/policies) diff --git a/docs/compliance/soc2/readiness-report.md b/docs/compliance/soc2/readiness-report.md new file mode 100644 index 0000000..e7b0d9c --- /dev/null +++ b/docs/compliance/soc2/readiness-report.md @@ -0,0 +1,50 @@ +# TrustSignal SOC 2 Readiness Report + +Generated: 2026-03-13T00:32:03.795Z + +> This report is an internal readiness snapshot aligned to SOC 2 Security criteria. It is intended for planning and gap remediation. It is not an audit opinion and does not imply SOC 2 certification. + +## Overall Readiness Score + +71% + +## Category Scores + +| Category | Score | Notes | +| --- | --- | --- | +| Access Control | 2 / 3 | Repository documentation covers branch protection and review expectations, but in-repo evidence does not prove completed access reviews or enforced GitHub settings. | +| Infrastructure Security | 2 / 3 | Repository-level security workflows exist for dependency review, Trivy, and Scorecard, but infrastructure controls still require manual verification outside the repo. | +| Secure Development | 3 / 3 | Pull request review guidance and security-focused CI checks provide strong repository-level secure development coverage for a readiness baseline. | +| Monitoring | 1 / 3 | The repository can generate a security posture snapshot and retain CI scan outputs, but ongoing production monitoring evidence is not proven by repository files alone. | +| Secrets Management | 2 / 3 | TrustSignal guidance prohibits hardcoded secrets and uses environment-based configuration, but rotation cadence and vault evidence are not yet captured in this framework. | +| Incident Response | 2 / 3 | A formal policy template exists, but exercised incident records, communication drills, and post-incident evidence are not yet included. | +| Data Protection | 2 / 3 | Data handling and retention guidance now exists, but applied retention schedules and production evidence still need to be collected. | +| Compliance Documentation | 3 / 3 | The repository contains a structured readiness framework, policy templates, and generated reporting suitable for a mock-audit baseline. | + +## Recommended Remediation Items + +- Access Control: Capture recurring access review evidence for GitHub and production systems. +- Access Control: Enable and verify branch protection or rulesets with required reviews on main. +- Infrastructure Security: Document environment hardening baselines and infrastructure ownership. +- Infrastructure Security: Capture operational evidence for backup, recovery, and hosted-service security settings. +- Monitoring: Document log review cadence, alert routing, and monitored systems. +- Monitoring: Attach monitoring exports or screenshots for operational environments. +- Secrets Management: Track secret rotation ownership and review cadence. +- Secrets Management: Collect evidence that production secrets are stored and rotated using approved mechanisms. +- Incident Response: Run a tabletop exercise and retain the output. +- Incident Response: Define severity levels, contact paths, and evidence preservation procedures in operating records. +- Data Protection: Define retention windows by evidence and operational data category. +- Data Protection: Capture proof of encryption, access controls, and disposal procedures where applicable. + +## Scoring Model + +- 0 = missing +- 1 = partial +- 2 = implemented +- 3 = strong + +## Notes + +- Scores are based on repository-visible controls and documentation only. +- GitHub UI configuration, infrastructure operations, access reviews, and restore testing still require manual verification. +- This report should be refreshed when major security workflows, policies, or governance controls change. diff --git a/scripts/security-readiness.ts b/scripts/security-readiness.ts new file mode 100644 index 0000000..1870e29 --- /dev/null +++ b/scripts/security-readiness.ts @@ -0,0 +1,101 @@ +import fs from "node:fs"; +import path from "node:path"; + +type CheckResult = { + status: "present" | "partial" | "missing"; + details: string; +}; + +const repoRoot = path.resolve(__dirname, ".."); + +function exists(relativePath: string): boolean { + return fs.existsSync(path.join(repoRoot, relativePath)); +} + +function evaluate(): Record { + const workflowsPresent = + exists(".github/workflows/dependency-review.yml") && + exists(".github/workflows/trivy.yml") && + exists(".github/workflows/scorecard.yml") && + exists(".github/workflows/zizmor.yml"); + + const dependencyScanning = + exists(".github/dependabot.yml") && exists(".github/workflows/dependency-review.yml"); + + const branchProtectionIndicators = + exists("docs/github-settings-checklist.md") && + (exists("scripts/apply-github-branch-protection.sh") || exists(".github/pull_request_template.md")); + + const ciSecurityTools = + exists(".github/workflows/trivy.yml") && + exists(".github/workflows/zizmor.yml") && + exists(".github/workflows/scorecard.yml"); + + return { + "GitHub workflows present": { + status: workflowsPresent ? "present" : "missing", + details: workflowsPresent + ? "Dependency Review, Trivy, Scorecard, and zizmor workflow files exist." + : "One or more expected workflow files are missing.", + }, + "Dependency scanning enabled": { + status: dependencyScanning ? "present" : "partial", + details: dependencyScanning + ? "Dependabot configuration and dependency review workflow are present in-repo." + : "Repository automation is incomplete or depends on manual GitHub feature enablement.", + }, + "Branch protection indicators": { + status: branchProtectionIndicators ? "partial" : "missing", + details: branchProtectionIndicators + ? "Repository contains documentation or helper automation for branch protection, but actual GitHub rules must be verified manually." + : "No branch protection guidance or helper indicators were found.", + }, + "CI security tools present": { + status: ciSecurityTools ? "present" : "partial", + details: ciSecurityTools + ? "Repository-level CI security tooling is present for vulnerabilities, Scorecard, and workflow linting." + : "Only part of the expected CI security tooling is present.", + }, + }; +} + +function renderMarkdown(results: Record): string { + const rows = Object.entries(results) + .map(([name, result]) => `| ${name} | ${result.status} | ${result.details} |`) + .join("\n"); + + return `# TrustSignal Security Posture Snapshot + +Generated: ${new Date().toISOString()} + +> This report summarizes repository-visible security governance indicators. It is a posture snapshot, not proof that all related GitHub or infrastructure settings are enabled in production. + +## Checks + +| Check | Status | Details | +| --- | --- | --- | +${rows} + +## Interpretation + +- \`present\` means the expected repository file or automation exists. +- \`partial\` means repository indicators exist, but the control still depends on manual GitHub or infrastructure verification. +- \`missing\` means the repository does not currently provide the expected indicator. + +## Manual Follow-Up + +- Verify branch protection or rulesets directly in GitHub. +- Verify Dependency Graph, Dependabot alerts, and code scanning are enabled in repository settings. +- Capture dated screenshots or exports if the result will be used as audit evidence. +`; +} + +function main(): void { + const results = evaluate(); + const outputPath = path.join(repoRoot, "docs/compliance/security-posture.md"); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, renderMarkdown(results)); + console.log(JSON.stringify(results, null, 2)); +} + +main(); diff --git a/scripts/soc2-readiness.ts b/scripts/soc2-readiness.ts new file mode 100644 index 0000000..9c5f1c8 --- /dev/null +++ b/scripts/soc2-readiness.ts @@ -0,0 +1,184 @@ +import fs from "node:fs"; +import path from "node:path"; + +type CategoryName = + | "Access Control" + | "Infrastructure Security" + | "Secure Development" + | "Monitoring" + | "Secrets Management" + | "Incident Response" + | "Data Protection" + | "Compliance Documentation"; + +type CategoryResult = { + score: 0 | 1 | 2 | 3; + rationale: string; + remediation: string[]; +}; + +const repoRoot = path.resolve(__dirname, ".."); + +function exists(relativePath: string): boolean { + return fs.existsSync(path.join(repoRoot, relativePath)); +} + +function categoryResults(): Record { + return { + "Access Control": { + score: exists("docs/github-settings-checklist.md") ? 2 : 1, + rationale: + "Repository documentation covers branch protection and review expectations, but in-repo evidence does not prove completed access reviews or enforced GitHub settings.", + remediation: [ + "Capture recurring access review evidence for GitHub and production systems.", + "Enable and verify branch protection or rulesets with required reviews on main.", + ], + }, + "Infrastructure Security": { + score: exists(".github/workflows/trivy.yml") && exists(".github/workflows/scorecard.yml") ? 2 : 1, + rationale: + "Repository-level security workflows exist for dependency review, Trivy, and Scorecard, but infrastructure controls still require manual verification outside the repo.", + remediation: [ + "Document environment hardening baselines and infrastructure ownership.", + "Capture operational evidence for backup, recovery, and hosted-service security settings.", + ], + }, + "Secure Development": { + score: + exists(".github/pull_request_template.md") && + exists(".github/workflows/dependency-review.yml") && + exists(".github/workflows/zizmor.yml") + ? 3 + : 1, + rationale: + "Pull request review guidance and security-focused CI checks provide strong repository-level secure development coverage for a readiness baseline.", + remediation: [ + "Add documented secure code review ownership and periodic retrospective review of findings.", + ], + }, + Monitoring: { + score: exists("docs/compliance/security-posture.md") ? 2 : 1, + rationale: + "The repository can generate a security posture snapshot and retain CI scan outputs, but ongoing production monitoring evidence is not proven by repository files alone.", + remediation: [ + "Document log review cadence, alert routing, and monitored systems.", + "Attach monitoring exports or screenshots for operational environments.", + ], + }, + "Secrets Management": { + score: exists("SECURITY_CHECKLIST.md") || exists("apps/api/.env.example") ? 2 : 1, + rationale: + "TrustSignal guidance prohibits hardcoded secrets and uses environment-based configuration, but rotation cadence and vault evidence are not yet captured in this framework.", + remediation: [ + "Track secret rotation ownership and review cadence.", + "Collect evidence that production secrets are stored and rotated using approved mechanisms.", + ], + }, + "Incident Response": { + score: exists("docs/compliance/policies/incident-response-policy.md") ? 2 : 0, + rationale: + "A formal policy template exists, but exercised incident records, communication drills, and post-incident evidence are not yet included.", + remediation: [ + "Run a tabletop exercise and retain the output.", + "Define severity levels, contact paths, and evidence preservation procedures in operating records.", + ], + }, + "Data Protection": { + score: + exists("docs/compliance/policies/data-retention-policy.md") && exists("docs/security-summary.md") ? 2 : 1, + rationale: + "Data handling and retention guidance now exists, but applied retention schedules and production evidence still need to be collected.", + remediation: [ + "Define retention windows by evidence and operational data category.", + "Capture proof of encryption, access controls, and disposal procedures where applicable.", + ], + }, + "Compliance Documentation": { + score: + exists("docs/compliance/soc2/controls.md") && + exists("docs/compliance/soc2/readiness-checklist.md") && + exists("docs/compliance/soc2/readiness-report.md") + ? 3 + : 2, + rationale: + "The repository contains a structured readiness framework, policy templates, and generated reporting suitable for a mock-audit baseline.", + remediation: [ + "Assign document owners and refresh cadence for each policy and evidence tracker.", + ], + }, + }; +} + +function buildReport(): { markdown: string; percentage: number; results: Record } { + const results = categoryResults(); + const categoryEntries = Object.entries(results) as Array<[CategoryName, CategoryResult]>; + const totalScore = categoryEntries.reduce((sum, [, item]) => sum + item.score, 0); + const maxScore = categoryEntries.length * 3; + const percentage = Math.round((totalScore / maxScore) * 100); + + const remediationItems = categoryEntries + .flatMap(([name, item]) => + item.score < 3 ? item.remediation.map((entry) => `- ${name}: ${entry}`) : [], + ) + .join("\n"); + + const tableRows = categoryEntries + .map( + ([name, item]) => + `| ${name} | ${item.score} / 3 | ${item.rationale} |`, + ) + .join("\n"); + + const markdown = `# TrustSignal SOC 2 Readiness Report + +Generated: ${new Date().toISOString()} + +> This report is an internal readiness snapshot aligned to SOC 2 Security criteria. It is intended for planning and gap remediation. It is not an audit opinion and does not imply SOC 2 certification. + +## Overall Readiness Score + +${percentage}% + +## Category Scores + +| Category | Score | Notes | +| --- | --- | --- | +${tableRows} + +## Recommended Remediation Items + +${remediationItems} + +## Scoring Model + +- 0 = missing +- 1 = partial +- 2 = implemented +- 3 = strong + +## Notes + +- Scores are based on repository-visible controls and documentation only. +- GitHub UI configuration, infrastructure operations, access reviews, and restore testing still require manual verification. +- This report should be refreshed when major security workflows, policies, or governance controls change. +`; + + return { markdown, percentage, results }; +} + +function main(): void { + const outputPath = path.join(repoRoot, "docs/compliance/soc2/readiness-report.md"); + const { markdown, percentage, results } = buildReport(); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, markdown); + + const summary = { + generatedAt: new Date().toISOString(), + scorePercentage: percentage, + categories: results, + }; + + console.log(JSON.stringify(summary, null, 2)); +} + +main(); From f2f603322a3b418aaf313718682d3c8fa384f73f Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 19:35:25 -0500 Subject: [PATCH 14/25] docs: restrict public SOC2 evidence to placeholders --- docs/compliance/evidence/access-control-evidence.md | 7 ++++--- docs/compliance/evidence/ci-security-evidence.md | 5 +++-- docs/compliance/evidence/logging-monitoring.md | 5 +++-- docs/compliance/evidence/vulnerability-management.md | 7 ++++--- docs/compliance/soc2/controls.md | 3 +++ docs/compliance/soc2/readiness-checklist.md | 3 +++ 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/compliance/evidence/access-control-evidence.md b/docs/compliance/evidence/access-control-evidence.md index fff5e84..4130995 100644 --- a/docs/compliance/evidence/access-control-evidence.md +++ b/docs/compliance/evidence/access-control-evidence.md @@ -1,6 +1,6 @@ # TrustSignal Access Control Evidence Guide -This placeholder tracks the types of evidence auditors normally expect for access control testing. +This file is a placeholder only. Do not store access review data, employee access lists, approval records, or exported administrative evidence in the public repository. Expected evidence: - GitHub organization membership and repository access listings @@ -10,5 +10,6 @@ Expected evidence: - joiner, mover, and leaver tickets showing approval and removal actions TrustSignal notes: -- repository files can document expected controls, but GitHub UI settings and access review outputs must still be collected manually -- attach dated screenshots, exports, or ticket references here during audit preparation +- repository files can document expected controls, but GitHub UI settings and access review outputs must be stored in Vanta or approved internal compliance storage +- keep this file limited to a short pointer such as: "Current evidence is maintained in Vanta under Access Control / Quarterly Access Review" +- do not paste screenshots, exports, names, email addresses, or system-specific access data into this repository diff --git a/docs/compliance/evidence/ci-security-evidence.md b/docs/compliance/evidence/ci-security-evidence.md index f54c916..59b66fa 100644 --- a/docs/compliance/evidence/ci-security-evidence.md +++ b/docs/compliance/evidence/ci-security-evidence.md @@ -1,6 +1,6 @@ # TrustSignal CI Security Evidence Guide -This placeholder tracks evidence for CI-based security controls and secure change management. +This file is a placeholder only. Do not store exported CI logs, workflow run artifacts, or internal review records in the public repository. Expected evidence: - GitHub Actions run history for build, typecheck, and security workflows @@ -11,4 +11,5 @@ Expected evidence: TrustSignal notes: - current repository automation includes dependency review, Trivy, OpenSSF Scorecard, and zizmor -- collect successful and failed run examples to show operation and triage behavior +- store real workflow evidence in Vanta or approved internal compliance storage and keep this file limited to a pointer to that system of record +- do not paste raw workflow logs, internal approvals, or screenshots into this repository diff --git a/docs/compliance/evidence/logging-monitoring.md b/docs/compliance/evidence/logging-monitoring.md index 5f03de3..ac9dfb9 100644 --- a/docs/compliance/evidence/logging-monitoring.md +++ b/docs/compliance/evidence/logging-monitoring.md @@ -1,6 +1,6 @@ # TrustSignal Logging and Monitoring Evidence Guide -This placeholder tracks evidence for monitoring, detection, and operational review. +This file is a placeholder only. Do not store production logs, alert payloads, internal dashboard exports, or incident-linked monitoring records in the public repository. Expected evidence: - logging standards or operational runbooks @@ -11,4 +11,5 @@ Expected evidence: TrustSignal notes: - repository files can describe expectations, but auditors will typically expect operational evidence from deployed systems and review processes -- document where logs are retained, who reviews them, and how anomalies are escalated +- store real evidence in Vanta or approved internal compliance storage and keep this file limited to a pointer to the authoritative evidence location +- do not include private system architecture, dashboard screenshots, production log excerpts, or responder notes here diff --git a/docs/compliance/evidence/vulnerability-management.md b/docs/compliance/evidence/vulnerability-management.md index 3c33980..22202de 100644 --- a/docs/compliance/evidence/vulnerability-management.md +++ b/docs/compliance/evidence/vulnerability-management.md @@ -1,6 +1,6 @@ # TrustSignal Vulnerability Management Evidence Guide -This placeholder tracks evidence for identifying, reviewing, and remediating vulnerabilities. +This file is a placeholder only. Do not store internal vulnerability tickets, private scan exports, or remediation history with sensitive system detail in the public repository. Expected evidence: - dependency review results on pull requests @@ -10,5 +10,6 @@ Expected evidence: - review notes showing severity-based triage decisions TrustSignal notes: -- repository automation can surface findings, but remediation ownership and closure evidence must still be collected manually -- include examples of accepted risk, deferred remediation, and completed fixes where applicable +- repository automation can surface findings, but remediation ownership and closure evidence must be collected in Vanta or approved internal compliance storage +- keep this file limited to a short pointer to the current evidence location and review owner +- do not paste private findings, internal ticket links, or detailed remediation notes into this repository diff --git a/docs/compliance/soc2/controls.md b/docs/compliance/soc2/controls.md index 3d8a17a..0171437 100644 --- a/docs/compliance/soc2/controls.md +++ b/docs/compliance/soc2/controls.md @@ -2,6 +2,9 @@ > TrustSignal maintains this document as a readiness-oriented control map for the SOC 2 Security Trust Services Criteria. It is intended for internal assessment, partner diligence, and gap remediation planning. It is not a statement of SOC 2 certification. +> Public-repo boundary: +> This file contains high-level control descriptions only. Sensitive audit evidence, private architecture, access review data, production logs, and incident records must remain in Vanta or approved internal compliance storage. + Short description: This document maps observable TrustSignal practices, repository controls, and operating expectations to core SOC 2 Security control areas so teams can identify evidence, confirm coverage, and track remaining gaps. diff --git a/docs/compliance/soc2/readiness-checklist.md b/docs/compliance/soc2/readiness-checklist.md index 2a0ab72..53076d6 100644 --- a/docs/compliance/soc2/readiness-checklist.md +++ b/docs/compliance/soc2/readiness-checklist.md @@ -2,6 +2,9 @@ > This checklist is designed for mock-audit readiness reviews against SOC 2 Security criteria. It helps the team separate implemented controls from partially implemented controls and evidence that still depends on manual operations. +> Public-repo boundary: +> Use this checklist to track readiness at a high level only. Do not add internal infrastructure diagrams, employee access lists, production logs, incident reports, or other sensitive audit evidence here. + Short description: Use this checklist during internal security reviews, partner diligence preparation, and pre-audit cleanup to confirm whether TrustSignal has evidence-ready controls rather than undocumented intent. From 3f5d8ddd8d7686b44c463679cd124c15fc280c9c Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Thu, 12 Mar 2026 19:37:28 -0500 Subject: [PATCH 15/25] security: enforce private evidence boundary for SOC2 readiness docs --- docs/compliance/README.md | 46 +++++++++++++++++++ docs/compliance/evidence-boundary.md | 33 +++++++++++++ .../evidence/access-control-evidence.md | 19 +++----- .../evidence/ci-security-evidence.md | 19 +++----- .../compliance/evidence/logging-monitoring.md | 19 +++----- .../evidence/vulnerability-management.md | 19 +++----- .../policies/access-control-policy.md | 8 ++-- .../policies/data-retention-policy.md | 8 ++-- .../policies/incident-response-policy.md | 8 ++-- .../policies/secure-development-policy.md | 8 ++-- .../compliance/policies/vendor-risk-policy.md | 8 ++-- docs/compliance/security-posture.md | 2 +- docs/compliance/soc2/readiness-report.md | 1 + 13 files changed, 124 insertions(+), 74 deletions(-) create mode 100644 docs/compliance/README.md create mode 100644 docs/compliance/evidence-boundary.md diff --git a/docs/compliance/README.md b/docs/compliance/README.md new file mode 100644 index 0000000..4c7a3bf --- /dev/null +++ b/docs/compliance/README.md @@ -0,0 +1,46 @@ +# TrustSignal Compliance Documentation Boundary + +> This repository contains SOC 2 readiness guidance only. It does not contain audit evidence or sensitive security information. + +Short description: +Use the files in this directory for high-level control descriptions, policy templates, readiness checklists, and public-safe compliance guidance only. + +Real compliance evidence must be stored in a private system such as: + +- Vanta +- internal compliance storage +- a private audit repository + +Prohibited evidence types in this public repository: + +- employee access lists +- internal infrastructure diagrams +- production logs +- incident reports +- vulnerability reports +- secrets or key management details +- vendor contracts +- customer data artifacts + +## Public Repository Contents + +- high-level control descriptions +- policy templates +- readiness checklist and readiness scoring outputs +- public-safe compliance boundary guidance + +## Private Systems Contents + +- operational evidence +- access review logs +- incident case records +- infrastructure diagrams +- monitoring dashboards +- vendor due diligence records +- security findings and remediation history + +## Related Documentation + +- [Evidence boundary](/Users/christopher/Projects/trustsignal/docs/compliance/evidence-boundary.md) +- [SOC 2 controls](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/controls.md) +- [SOC 2 readiness checklist](/Users/christopher/Projects/trustsignal/docs/compliance/soc2/readiness-checklist.md) diff --git a/docs/compliance/evidence-boundary.md b/docs/compliance/evidence-boundary.md new file mode 100644 index 0000000..dce2da2 --- /dev/null +++ b/docs/compliance/evidence-boundary.md @@ -0,0 +1,33 @@ +# TrustSignal Compliance Evidence Boundary + +> TrustSignal separates public readiness documentation from private compliance evidence. The public repository must remain limited to high-level documentation and placeholders. + +## Public Repo + +- policy templates +- high-level control descriptions +- readiness checklist +- compliance overview +- generated public-safe readiness summaries + +## Private Systems + +- operational evidence +- access review logs +- security incident reports +- infrastructure diagrams +- monitoring dashboards +- vulnerability evidence and remediation records +- vendor contracts and due diligence records + +## Storage Expectation + +Real audit evidence should be stored in: + +- Vanta +- internal compliance storage +- private audit repository + +## Rule + +Do not place sensitive operational evidence in this repository. If a public compliance file needs to reference evidence, it should point to the private system of record rather than copying the evidence into git. diff --git a/docs/compliance/evidence/access-control-evidence.md b/docs/compliance/evidence/access-control-evidence.md index 4130995..855bd25 100644 --- a/docs/compliance/evidence/access-control-evidence.md +++ b/docs/compliance/evidence/access-control-evidence.md @@ -1,15 +1,10 @@ -# TrustSignal Access Control Evidence Guide +# TrustSignal Access Control Evidence Placeholder -This file is a placeholder only. Do not store access review data, employee access lists, approval records, or exported administrative evidence in the public repository. +Control Objective +Document that access to TrustSignal systems and repositories is approved, limited, reviewed, and removed on least-privilege terms. -Expected evidence: -- GitHub organization membership and repository access listings -- branch protection or ruleset configuration for protected branches -- reviewer requirements for pull requests -- access review logs or exported review records -- joiner, mover, and leaver tickets showing approval and removal actions +Evidence Expected by Auditor +Access review records, access approvals, repository protection settings, and joiner/mover/leaver evidence. -TrustSignal notes: -- repository files can document expected controls, but GitHub UI settings and access review outputs must be stored in Vanta or approved internal compliance storage -- keep this file limited to a short pointer such as: "Current evidence is maintained in Vanta under Access Control / Quarterly Access Review" -- do not paste screenshots, exports, names, email addresses, or system-specific access data into this repository +Where Evidence Is Stored +Vanta, internal compliance storage, or private audit repository. Do not store access review data, employee access lists, names, or exported administrative records in this public repository. diff --git a/docs/compliance/evidence/ci-security-evidence.md b/docs/compliance/evidence/ci-security-evidence.md index 59b66fa..c73d6d6 100644 --- a/docs/compliance/evidence/ci-security-evidence.md +++ b/docs/compliance/evidence/ci-security-evidence.md @@ -1,15 +1,10 @@ -# TrustSignal CI Security Evidence Guide +# TrustSignal CI Security Evidence Placeholder -This file is a placeholder only. Do not store exported CI logs, workflow run artifacts, or internal review records in the public repository. +Control Objective +Document that TrustSignal uses CI validation and security checks to review changes and detect security issues before merge. -Expected evidence: -- GitHub Actions run history for build, typecheck, and security workflows -- workflow definitions showing least-privilege permissions -- screenshots or exported records of required status checks -- evidence that failing checks block merges where intended -- change history for workflow updates +Evidence Expected by Auditor +Workflow run records, required status check configuration, workflow review history, and security scan outcomes. -TrustSignal notes: -- current repository automation includes dependency review, Trivy, OpenSSF Scorecard, and zizmor -- store real workflow evidence in Vanta or approved internal compliance storage and keep this file limited to a pointer to that system of record -- do not paste raw workflow logs, internal approvals, or screenshots into this repository +Where Evidence Is Stored +Vanta, internal compliance storage, or private audit repository. Do not store raw CI logs, screenshots, run artifacts, or internal approvals in this public repository. diff --git a/docs/compliance/evidence/logging-monitoring.md b/docs/compliance/evidence/logging-monitoring.md index ac9dfb9..7e22b82 100644 --- a/docs/compliance/evidence/logging-monitoring.md +++ b/docs/compliance/evidence/logging-monitoring.md @@ -1,15 +1,10 @@ -# TrustSignal Logging and Monitoring Evidence Guide +# TrustSignal Logging and Monitoring Evidence Placeholder -This file is a placeholder only. Do not store production logs, alert payloads, internal dashboard exports, or incident-linked monitoring records in the public repository. +Control Objective +Document that TrustSignal monitors security-relevant activity and retains reviewable monitoring evidence through approved operational systems. -Expected evidence: -- logging standards or operational runbooks -- examples of security-relevant logs or alerts -- monitoring dashboard screenshots or exported views -- incident escalation records tied to monitored events -- evidence of periodic review for monitoring outputs +Evidence Expected by Auditor +Monitoring procedures, alert review records, dashboard evidence, and logging review records. -TrustSignal notes: -- repository files can describe expectations, but auditors will typically expect operational evidence from deployed systems and review processes -- store real evidence in Vanta or approved internal compliance storage and keep this file limited to a pointer to the authoritative evidence location -- do not include private system architecture, dashboard screenshots, production log excerpts, or responder notes here +Where Evidence Is Stored +Vanta, internal compliance storage, or private audit repository. Do not store production logs, dashboard exports, alert payloads, or private system architecture in this public repository. diff --git a/docs/compliance/evidence/vulnerability-management.md b/docs/compliance/evidence/vulnerability-management.md index 22202de..1e6b12f 100644 --- a/docs/compliance/evidence/vulnerability-management.md +++ b/docs/compliance/evidence/vulnerability-management.md @@ -1,15 +1,10 @@ -# TrustSignal Vulnerability Management Evidence Guide +# TrustSignal Vulnerability Management Evidence Placeholder -This file is a placeholder only. Do not store internal vulnerability tickets, private scan exports, or remediation history with sensitive system detail in the public repository. +Control Objective +Document that TrustSignal identifies, reviews, and remediates vulnerabilities using approved workflows and tracked remediation processes. -Expected evidence: -- dependency review results on pull requests -- Dependabot alerts or update pull requests -- Trivy scan results -- issue tracking or remediation tickets for material findings -- review notes showing severity-based triage decisions +Evidence Expected by Auditor +Security scan results, dependency review records, remediation tracking, and risk acceptance records where applicable. -TrustSignal notes: -- repository automation can surface findings, but remediation ownership and closure evidence must be collected in Vanta or approved internal compliance storage -- keep this file limited to a short pointer to the current evidence location and review owner -- do not paste private findings, internal ticket links, or detailed remediation notes into this repository +Where Evidence Is Stored +Vanta, internal compliance storage, or private audit repository. Do not store vulnerability reports, private findings, internal ticket links, or detailed remediation notes in this public repository. diff --git a/docs/compliance/policies/access-control-policy.md b/docs/compliance/policies/access-control-policy.md index 73b9e48..5471f2b 100644 --- a/docs/compliance/policies/access-control-policy.md +++ b/docs/compliance/policies/access-control-policy.md @@ -1,5 +1,7 @@ # TrustSignal Access Control Policy +> This public policy is intentionally high level. Operational access evidence and system-specific details must remain in private compliance systems. + ## Purpose Define how TrustSignal grants, reviews, and removes access to code, infrastructure, environments, and sensitive operational tooling. @@ -26,8 +28,4 @@ This policy applies to employees, contractors, service accounts, and third parti ## Evidence -- access request and approval records -- periodic access review logs -- repository membership or team export -- branch protection or ruleset screenshots -- deprovisioning records for departed personnel +Evidence for this policy must be stored in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. diff --git a/docs/compliance/policies/data-retention-policy.md b/docs/compliance/policies/data-retention-policy.md index 94924ad..4b3e7e3 100644 --- a/docs/compliance/policies/data-retention-policy.md +++ b/docs/compliance/policies/data-retention-policy.md @@ -1,5 +1,7 @@ # TrustSignal Data Retention Policy +> This public policy is intentionally high level. Storage-specific evidence and operational records must remain in private compliance systems. + ## Purpose Define retention, review, and disposal expectations for TrustSignal operational records, compliance evidence, and security-relevant data. @@ -24,8 +26,4 @@ This policy applies to repository evidence, audit artifacts, logs, incident reco ## Evidence -- retention schedule or matrix -- evidence inventory -- storage access reviews -- deletion or archival procedures -- examples of retained audit records +Evidence for this policy must be stored in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. diff --git a/docs/compliance/policies/incident-response-policy.md b/docs/compliance/policies/incident-response-policy.md index b2537d3..256d0df 100644 --- a/docs/compliance/policies/incident-response-policy.md +++ b/docs/compliance/policies/incident-response-policy.md @@ -1,5 +1,7 @@ # TrustSignal Incident Response Policy +> This public policy is intentionally high level. Incident records, responder notes, and operational investigation details must remain in private compliance systems. + ## Purpose Define a repeatable process for identifying, escalating, containing, investigating, and recovering from security incidents affecting TrustSignal systems or evidence integrity infrastructure. @@ -26,8 +28,4 @@ This policy applies to suspected or confirmed incidents involving TrustSignal co ## Evidence -- incident tickets or case records -- timelines and escalation logs -- forensic or log preservation notes -- post-incident review documents -- remediation tracking records +Evidence for this policy must be stored in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. diff --git a/docs/compliance/policies/secure-development-policy.md b/docs/compliance/policies/secure-development-policy.md index 54ac1ea..18f4aa3 100644 --- a/docs/compliance/policies/secure-development-policy.md +++ b/docs/compliance/policies/secure-development-policy.md @@ -1,5 +1,7 @@ # TrustSignal Secure Development Policy +> This public policy is intentionally high level. Tool-specific configurations, sensitive findings, and operational review records must remain in private compliance systems. + ## Purpose Establish secure software development expectations for TrustSignal so code changes are reviewed, tested, and released with documented security considerations. @@ -26,8 +28,4 @@ This policy applies to application code, infrastructure-as-code, CI/CD workflows ## Evidence -- pull request records and review comments -- CI build and validation history -- dependency review outputs -- security workflow logs -- remediation tickets for identified issues +Evidence for this policy must be stored in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. diff --git a/docs/compliance/policies/vendor-risk-policy.md b/docs/compliance/policies/vendor-risk-policy.md index e28c46f..c6d03ad 100644 --- a/docs/compliance/policies/vendor-risk-policy.md +++ b/docs/compliance/policies/vendor-risk-policy.md @@ -1,5 +1,7 @@ # TrustSignal Vendor Risk Policy +> This public policy is intentionally high level. Vendor contracts, assessments, and due diligence records must remain in private compliance systems. + ## Purpose Define how TrustSignal evaluates and monitors third-party vendors, hosted services, and material software dependencies that may affect security posture or service integrity. @@ -24,8 +26,4 @@ This policy applies to cloud providers, development tooling vendors, security to ## Evidence -- vendor inventory -- risk review worksheets -- security questionnaire responses or attestations -- renewal and reassessment notes -- remediation tracking for vendor findings +Evidence for this policy must be stored in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. diff --git a/docs/compliance/security-posture.md b/docs/compliance/security-posture.md index 065f3e4..885c292 100644 --- a/docs/compliance/security-posture.md +++ b/docs/compliance/security-posture.md @@ -23,4 +23,4 @@ Generated: 2026-03-13T00:32:09.914Z - Verify branch protection or rulesets directly in GitHub. - Verify Dependency Graph, Dependabot alerts, and code scanning are enabled in repository settings. -- Capture dated screenshots or exports if the result will be used as audit evidence. +- Store any operational evidence in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. diff --git a/docs/compliance/soc2/readiness-report.md b/docs/compliance/soc2/readiness-report.md index e7b0d9c..dd9f8e3 100644 --- a/docs/compliance/soc2/readiness-report.md +++ b/docs/compliance/soc2/readiness-report.md @@ -47,4 +47,5 @@ Generated: 2026-03-13T00:32:03.795Z - Scores are based on repository-visible controls and documentation only. - GitHub UI configuration, infrastructure operations, access reviews, and restore testing still require manual verification. +- Operational evidence must be stored in Vanta, internal compliance storage, or a private audit repository rather than in this public repository. - This report should be refreshed when major security workflows, policies, or governance controls change. From 3176355d80c8b6d37736a75be2cc7e8e14033b0d Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 01:19:46 -0500 Subject: [PATCH 16/25] feat: finish github action setup for generic artifact verification --- docs/integrations/github-action.md | 118 ++++++++ .../trustsignal-verify-artifact/README.md | 177 +++++++++++ .../trustsignal-verify-artifact/action.yml | 42 +++ .../trustsignal-verify-artifact/package.json | 32 ++ .../scripts/mock-fetch.js | 50 ++++ .../scripts/test-local.js | 105 +++++++ .../trustsignal-verify-artifact/src/index.js | 281 ++++++++++++++++++ 7 files changed, 805 insertions(+) create mode 100644 docs/integrations/github-action.md create mode 100644 github-actions/trustsignal-verify-artifact/README.md create mode 100644 github-actions/trustsignal-verify-artifact/action.yml create mode 100644 github-actions/trustsignal-verify-artifact/package.json create mode 100644 github-actions/trustsignal-verify-artifact/scripts/mock-fetch.js create mode 100644 github-actions/trustsignal-verify-artifact/scripts/test-local.js create mode 100644 github-actions/trustsignal-verify-artifact/src/index.js diff --git a/docs/integrations/github-action.md b/docs/integrations/github-action.md new file mode 100644 index 0000000..7ee2e02 --- /dev/null +++ b/docs/integrations/github-action.md @@ -0,0 +1,118 @@ +# TrustSignal GitHub Action Integration + +## Purpose + +`TrustSignal Verify Artifact` is a GitHub Action for verifying build artifacts with TrustSignal. It computes or accepts a SHA-256 digest for an artifact, sends that artifact identity to TrustSignal, and returns receipt metadata that a workflow can persist for later integrity checks. + +TrustSignal remains a neutral evidence integrity platform. The action uses a generic artifact verification contract and does not depend on domain-specific schemas. + +## Artifact Verification Flow + +1. The workflow passes either `artifact_path` or `artifact_hash`. +2. The action computes a SHA-256 digest when a local path is provided. +3. The action builds a generic verification payload with artifact identity plus GitHub workflow context. +4. The action sends `POST /api/v1/verify` with the TrustSignal API key in the `x-api-key` header. +5. TrustSignal returns verification metadata and a signed receipt reference. +6. The workflow consumes the action outputs or stores the `receipt_id` for later verification. + +## Request Contract Used By The Action + +Endpoint: + +```http +POST /api/v1/verify +``` + +Headers: + +```http +x-api-key: +content-type: application/json +``` + +Request body: + +```json +{ + "artifact": { + "hash": "", + "algorithm": "sha256" + }, + "source": { + "provider": "github-actions", + "repository": "", + "workflow": "", + "runId": "", + "commit": "", + "actor": "" + }, + "metadata": { + "artifactPath": "" + } +} +``` + +## Outputs + +| Output | Meaning | +| --- | --- | +| `verification_id` | TrustSignal verification identifier returned by the API. | +| `status` | Verification status returned by the API. | +| `receipt_id` | Signed receipt identifier returned by the API. | +| `receipt_signature` | Receipt signature returned by the API. | + +If the API does not return a distinct verification identifier, the action exposes `verification_id` as a compatibility alias to `receipt_id`. + +## Example Workflow + +```yaml +name: Verify Artifact + +on: + push: + branches: [main] + +jobs: + verify: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build artifact + run: | + mkdir -p dist + echo "build output" > dist/app.txt + + - name: Verify artifact with TrustSignal + id: trustsignal + uses: trustsignal-dev/trustsignal-verify-artifact@v1 + with: + api_base_url: https://api.trustsignal.dev + api_key: ${{ secrets.TRUSTSIGNAL_API_KEY }} + artifact_path: dist/app.txt + source: github-actions + fail_on_mismatch: "true" + + - name: Record receipt + run: | + echo "Verification ID: ${{ steps.trustsignal.outputs.verification_id }}" + echo "Receipt ID: ${{ steps.trustsignal.outputs.receipt_id }}" + echo "Receipt signature: ${{ steps.trustsignal.outputs.receipt_signature }}" +``` + +## Current Limitations + +- The local contract test uses a fetch mock rather than a live TrustSignal deployment. +- This package currently lives in a monorepo subdirectory, so it is not directly publishable to GitHub Marketplace. +- A production-facing integration test against a deployed TrustSignal environment is still pending. + +## Next Steps Before Marketplace Publication + +- Extract the package into a dedicated public repository. +- Move `action.yml` to the repository root in that dedicated repository. +- Tag versioned releases and commit `dist/index.js` for each published release. +- Add a live integration test against a deployed TrustSignal verification endpoint. diff --git a/github-actions/trustsignal-verify-artifact/README.md b/github-actions/trustsignal-verify-artifact/README.md new file mode 100644 index 0000000..e946a18 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/README.md @@ -0,0 +1,177 @@ +# TrustSignal Verify Artifact + +`TrustSignal Verify Artifact` is a JavaScript GitHub Action for generic build artifact verification. It hashes a build artifact or accepts a precomputed SHA-256 digest, submits that identity to TrustSignal, and returns signed receipt metadata to the workflow. + +TrustSignal issues signed verification receipts for the artifact identity you submit. Store the returned `receipt_id` if you want to support later verification flows in downstream systems. + +## What This Action Does + +- Verifies build artifacts with a generic TrustSignal artifact verification contract. +- Accepts either a local artifact path or a precomputed SHA-256 hash. +- Adds GitHub workflow context automatically when it is available in the runtime environment. +- Returns receipt metadata that can be persisted for later verification. + +## Inputs + +| Input | Required | Description | +| --- | --- | --- | +| `api_base_url` | Yes | Base URL for the TrustSignal public API, such as `https://api.trustsignal.dev`. | +| `api_key` | Yes | API key for the TrustSignal public verification endpoint. Store this in GitHub Actions secrets. | +| `artifact_path` | No | Local path to the artifact file to hash with SHA-256. | +| `artifact_hash` | No | Precomputed SHA-256 digest to verify instead of hashing a local file. | +| `source` | No | Source provider label attached to the verification request. Defaults to `github-actions`. | +| `fail_on_mismatch` | No | When `true`, fails the workflow if TrustSignal does not return a valid verification result. Defaults to `true`. | + +Provide exactly one of `artifact_path` or `artifact_hash`. + +## Outputs + +| Output | Description | +| --- | --- | +| `verification_id` | Verification identifier returned by TrustSignal. | +| `status` | Verification status normalized from the TrustSignal API response. | +| `receipt_id` | Receipt identifier returned by TrustSignal. | +| `receipt_signature` | Receipt signature returned by TrustSignal. | + +Compatibility note: + +- `verification_id` falls back to `receipt_id` when the API does not return a distinct verification identifier. + +## Request Contract + +The action sends `POST /api/v1/verify` with this body shape: + +```json +{ + "artifact": { + "hash": "", + "algorithm": "sha256" + }, + "source": { + "provider": "github-actions", + "repository": "", + "workflow": "", + "runId": "", + "commit": "", + "actor": "" + }, + "metadata": { + "artifactPath": "" + } +} +``` + +The action adds `source.repository`, `source.workflow`, `source.runId`, `source.commit`, and `source.actor` automatically from the GitHub Actions runtime when those values are available. + +## Example Workflow Using `artifact_path` + +```yaml +name: Verify Build Artifact + +on: + push: + branches: [main] + +jobs: + verify-artifact: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build artifact + run: | + mkdir -p dist + echo "release bundle" > dist/release.txt + + - name: Verify artifact with TrustSignal + id: trustsignal + uses: trustsignal-dev/trustsignal-verify-artifact@v1 + with: + api_base_url: https://api.trustsignal.dev + api_key: ${{ secrets.TRUSTSIGNAL_API_KEY }} + artifact_path: dist/release.txt + source: github-actions + fail_on_mismatch: "true" + + - name: Print receipt metadata + run: | + echo "Verification: ${{ steps.trustsignal.outputs.verification_id }}" + echo "Status: ${{ steps.trustsignal.outputs.status }}" + echo "Receipt: ${{ steps.trustsignal.outputs.receipt_id }}" +``` + +## Example Workflow Using `artifact_hash` + +```yaml +name: Verify Precomputed Hash + +on: + workflow_dispatch: + +jobs: + verify-artifact-hash: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Verify known artifact digest + id: trustsignal + uses: trustsignal-dev/trustsignal-verify-artifact@v1 + with: + api_base_url: https://api.trustsignal.dev + api_key: ${{ secrets.TRUSTSIGNAL_API_KEY }} + artifact_hash: 2f77668a9dfbf8d5847cf2d5d0370740e0c0601b4f061c1181f58c77c2b8f486 + source: github-actions + fail_on_mismatch: "true" + + - name: Print result + run: | + echo "Verification: ${{ steps.trustsignal.outputs.verification_id }}" + echo "Status: ${{ steps.trustsignal.outputs.status }}" +``` + +Later verification is possible by storing the returned `receipt_id` and using it with TrustSignal APIs that expose receipt lookup or verification. + +## Security Notes + +- The action never logs the API key. +- The API key is sent only in the `x-api-key` header. +- API failures are surfaced with concise HTTP status messages and without raw internal details. +- Inputs are validated before any request is sent. +- Local artifact hashing uses SHA-256 from Node.js `crypto`. + +## Current Limitations + +- This package is an integration scaffold and assumes a TrustSignal deployment that supports the generic artifact verification contract documented here. +- A live deployed API integration test is still pending; the included local test uses a fetch mock that validates the contract shape and action behavior. +- This package currently lives under `github-actions/trustsignal-verify-artifact/` inside the TrustSignal monorepo, so it is not yet directly publishable to GitHub Marketplace. + +## Local Validation + +Run the local syntax checks and contract test with: + +```bash +node --check src/index.js +node --check dist/index.js +node scripts/test-local.js +``` + +Or use the package scripts: + +```bash +npm run check +npm run test:local +npm run validate:local +``` + +## Marketplace Readiness Status + +- Not Marketplace-ready while it lives under `github-actions/trustsignal-verify-artifact/` inside the TrustSignal monorepo. +- Marketplace publication requires a dedicated public repository with `action.yml` at the repository root. +- Marketplace publication also requires release tagging, committed `dist/index.js` artifacts, and final public repository metadata. +- Update the README and integration doc when the public TrustSignal API contract or output fields change. diff --git a/github-actions/trustsignal-verify-artifact/action.yml b/github-actions/trustsignal-verify-artifact/action.yml new file mode 100644 index 0000000..b0ee087 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/action.yml @@ -0,0 +1,42 @@ +name: TrustSignal Verify Artifact +description: Verify build artifacts with TrustSignal and capture signed verification receipt metadata in GitHub Actions. +author: TrustSignal +branding: + icon: shield + color: blue + +inputs: + api_base_url: + description: Base URL for the TrustSignal public API. + required: true + api_key: + description: API key for the TrustSignal public API. + required: true + artifact_path: + description: Local path to the artifact to hash and verify. + required: false + artifact_hash: + description: Precomputed artifact hash to verify instead of hashing a file. + required: false + source: + description: Source label for the artifact verification request. + required: false + default: github-actions + fail_on_mismatch: + description: Fail the action when TrustSignal does not return a valid verification result. + required: false + default: "true" + +outputs: + verification_id: + description: TrustSignal verification identifier returned by the API, or a compatibility alias to receipt_id when the API omits a separate verification id. + status: + description: Normalized verification status returned by TrustSignal. + receipt_id: + description: TrustSignal receipt identifier returned by the API. + receipt_signature: + description: Signed receipt signature returned by the API. + +runs: + using: node20 + main: dist/index.js diff --git a/github-actions/trustsignal-verify-artifact/package.json b/github-actions/trustsignal-verify-artifact/package.json new file mode 100644 index 0000000..14596c4 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/package.json @@ -0,0 +1,32 @@ +{ + "name": "trustsignal-verify-artifact", + "version": "0.1.0", + "description": "GitHub Action that issues and verifies TrustSignal receipts for build artifacts.", + "main": "dist/index.js", + "type": "commonjs", + "files": [ + "action.yml", + "dist", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "mkdir -p dist && cp src/index.js dist/index.js", + "package": "npm run build", + "check": "node --check src/index.js && node --check dist/index.js", + "test:local": "node scripts/test-local.js", + "validate:local": "npm run check && npm run test:local" + }, + "keywords": [ + "github-action", + "trustsignal", + "verification", + "supply-chain", + "artifact" + ], + "author": "TrustSignal", + "license": "MIT", + "engines": { + "node": ">=20" + } +} diff --git a/github-actions/trustsignal-verify-artifact/scripts/mock-fetch.js b/github-actions/trustsignal-verify-artifact/scripts/mock-fetch.js new file mode 100644 index 0000000..214f7ac --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/scripts/mock-fetch.js @@ -0,0 +1,50 @@ +const crypto = require('node:crypto'); + +function sha256(value) { + return crypto.createHash('sha256').update(value).digest('hex'); +} + +function jsonResponse(status, body) { + return { + ok: status >= 200 && status < 300, + status, + async text() { + return JSON.stringify(body); + } + }; +} + +global.fetch = async function mockFetch(url, options = {}) { + const parsedUrl = new URL(url); + const apiKey = options.headers && (options.headers['x-api-key'] || options.headers['X-API-Key']); + if (apiKey !== 'test-key') { + return jsonResponse(403, { error: 'Forbidden: invalid API key' }); + } + + if (parsedUrl.pathname === '/api/v1/verify' && options.method === 'POST') { + const payload = JSON.parse(options.body || '{}'); + const receiptId = process.env.MOCK_RECEIPT_ID || '00000000-0000-4000-8000-000000000001'; + const verificationId = process.env.MOCK_VERIFICATION_ID || `verify-${receiptId}`; + const validHash = process.env.MOCK_VALID_ARTIFACT_HASH || sha256('valid artifact'); + const isValid = + payload?.artifact?.hash === validHash && + payload?.artifact?.algorithm === 'sha256' && + payload?.source?.provider === 'local-test' && + payload?.source?.repository === 'trustsignal-dev/trustsignal-verify-artifact' && + payload?.source?.workflow === 'Artifact Verification' && + payload?.source?.runId === '12345' && + payload?.source?.actor === 'octocat' && + payload?.source?.commit === 'abc123def456' && + typeof payload?.metadata?.artifactPath === 'string'; + + return jsonResponse(200, { + verification_id: verificationId, + status: isValid ? 'verified' : 'invalid', + receipt_id: receiptId, + receipt_signature: `sig-${receiptId}`, + valid: isValid + }); + } + + return jsonResponse(404, { error: 'not_found' }); +}; diff --git a/github-actions/trustsignal-verify-artifact/scripts/test-local.js b/github-actions/trustsignal-verify-artifact/scripts/test-local.js new file mode 100644 index 0000000..c5e981e --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/scripts/test-local.js @@ -0,0 +1,105 @@ +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { spawnSync } = require('node:child_process'); + +function readOutputs(filePath) { + const raw = fs.readFileSync(filePath, 'utf8'); + return Object.fromEntries( + raw + .trim() + .split('\n') + .filter(Boolean) + .map((line) => { + const index = line.indexOf('='); + return [line.slice(0, index), line.slice(index + 1)]; + }) + ); +} + +function runAction({ artifactContents, failOnMismatch, receiptId }) { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'trustsignal-action-')); + const artifactPath = path.join(tempDir, 'artifact.txt'); + const outputPath = path.join(tempDir, 'github-output.txt'); + fs.writeFileSync(artifactPath, artifactContents, 'utf8'); + + const result = spawnSync( + process.execPath, + ['-r', './scripts/mock-fetch.js', 'dist/index.js'], + { + cwd: path.resolve(__dirname, '..'), + env: { + ...process.env, + INPUT_API_BASE_URL: 'https://api.trustsignal.dev', + INPUT_API_KEY: 'test-key', + INPUT_ARTIFACT_PATH: artifactPath, + INPUT_SOURCE: 'local-test', + INPUT_FAIL_ON_MISMATCH: String(failOnMismatch), + GITHUB_OUTPUT: outputPath, + GITHUB_RUN_ID: '12345', + GITHUB_REPOSITORY: 'trustsignal-dev/trustsignal-verify-artifact', + GITHUB_WORKFLOW: 'Artifact Verification', + GITHUB_ACTOR: 'octocat', + GITHUB_SHA: 'abc123def456', + MOCK_RECEIPT_ID: receiptId + }, + encoding: 'utf8' + } + ); + + const outputs = fs.existsSync(outputPath) ? readOutputs(outputPath) : {}; + return { + status: result.status, + stdout: result.stdout, + stderr: result.stderr, + outputs + }; +} + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function main() { + const validRun = runAction({ + artifactContents: 'valid artifact', + failOnMismatch: true, + receiptId: '00000000-0000-4000-8000-000000000001' + }); + + const tamperedRun = runAction({ + artifactContents: 'tampered artifact', + failOnMismatch: false, + receiptId: '00000000-0000-4000-8000-000000000002' + }); + + const failingMismatchRun = runAction({ + artifactContents: 'tampered artifact', + failOnMismatch: true, + receiptId: '00000000-0000-4000-8000-000000000003' + }); + + assert(validRun.status === 0, `Expected valid run to succeed, got ${validRun.status}: ${validRun.stderr}`); + assert(validRun.outputs.verification_id === 'verify-00000000-0000-4000-8000-000000000001', 'Valid run verification_id mismatch'); + assert(validRun.outputs.receipt_id === '00000000-0000-4000-8000-000000000001', 'Valid run receipt_id mismatch'); + assert(validRun.outputs.status === 'verified', `Expected valid status to be verified, got ${validRun.outputs.status}`); + assert(validRun.outputs.receipt_signature === 'sig-00000000-0000-4000-8000-000000000001', 'Valid run receipt_signature mismatch'); + + assert(tamperedRun.status === 0, `Expected tampered run to complete when fail_on_mismatch=false, got ${tamperedRun.status}: ${tamperedRun.stderr}`); + assert(tamperedRun.outputs.verification_id === 'verify-00000000-0000-4000-8000-000000000002', 'Tampered run verification_id mismatch'); + assert(tamperedRun.outputs.receipt_id === '00000000-0000-4000-8000-000000000002', 'Tampered run receipt_id mismatch'); + assert(tamperedRun.outputs.status === 'invalid', `Expected tampered status to be invalid, got ${tamperedRun.outputs.status}`); + assert(tamperedRun.outputs.receipt_signature === 'sig-00000000-0000-4000-8000-000000000002', 'Tampered run receipt_signature mismatch'); + assert(failingMismatchRun.status !== 0, 'Expected mismatch run to fail when fail_on_mismatch=true'); + + process.stdout.write('Local action contract test passed\n'); +} + +try { + main(); +} catch (error) { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); + process.exit(1); +} diff --git a/github-actions/trustsignal-verify-artifact/src/index.js b/github-actions/trustsignal-verify-artifact/src/index.js new file mode 100644 index 0000000..05625ea --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/src/index.js @@ -0,0 +1,281 @@ +const crypto = require('node:crypto'); +const fs = require('node:fs'); +const path = require('node:path'); + +function getInput(name, options = {}) { + const envName = `INPUT_${name.replace(/ /g, '_').toUpperCase()}`; + const raw = process.env[envName]; + const value = typeof raw === 'string' ? raw.trim() : ''; + + if (options.required && !value) { + throw new Error(`Missing required input: ${name}`); + } + + return value; +} + +function getBooleanInput(name, defaultValue = false) { + const value = getInput(name); + if (!value) return defaultValue; + + const normalized = value.toLowerCase(); + if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) return true; + if (['false', '0', 'no', 'n', 'off'].includes(normalized)) return false; + + throw new Error(`Invalid boolean input for ${name}: expected true or false`); +} + +function setOutput(name, value) { + const outputPath = process.env.GITHUB_OUTPUT; + if (!outputPath) { + process.stdout.write(`${name}=${value}\n`); + return; + } + + fs.appendFileSync(outputPath, `${name}=${String(value ?? '')}\n`, 'utf8'); +} + +function setFailed(message) { + process.stderr.write(`::error::${message}\n`); + process.exitCode = 1; +} + +function sha256File(filePath) { + const absolutePath = path.resolve(filePath); + if (!fs.existsSync(absolutePath)) { + throw new Error(`Artifact file not found: ${absolutePath}`); + } + + const stats = fs.statSync(absolutePath); + if (!stats.isFile()) { + throw new Error(`Artifact path is not a file: ${absolutePath}`); + } + + const hash = crypto.createHash('sha256'); + const fileBuffer = fs.readFileSync(absolutePath); + hash.update(fileBuffer); + return hash.digest('hex'); +} + +function validateHash(value) { + const normalized = value.toLowerCase().replace(/^sha256:/, ''); + if (!/^[a-f0-9]{64}$/.test(normalized)) { + throw new Error('artifact_hash must be a valid SHA-256 hex digest'); + } + return normalized; +} + +function normalizeBaseUrl(value) { + let url; + + try { + url = new URL(value); + } catch { + throw new Error('api_base_url must be a valid URL'); + } + + if (!/^https?:$/.test(url.protocol)) { + throw new Error('api_base_url must use http or https'); + } + + url.pathname = url.pathname.replace(/\/+$/, ''); + url.search = ''; + url.hash = ''; + return url.toString().replace(/\/$/, ''); +} + +function getGitHubContext() { + return { + repository: process.env.GITHUB_REPOSITORY || undefined, + runId: process.env.GITHUB_RUN_ID || undefined, + workflow: process.env.GITHUB_WORKFLOW || undefined, + actor: process.env.GITHUB_ACTOR || undefined, + sha: process.env.GITHUB_SHA || undefined + }; +} + +function buildVerificationRequest({ artifactHash, artifactPath, source }) { + const github = getGitHubContext(); + const provider = source.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 64) || 'github-actions'; + + return { + artifact: { + hash: artifactHash, + algorithm: 'sha256' + }, + source: { + provider, + repository: github.repository, + workflow: github.workflow, + runId: github.runId, + actor: github.actor, + commit: github.sha + }, + metadata: { + ...(artifactPath ? { artifactPath } : {}) + } + }; +} + +function deriveStatus(responseBody) { + return ( + responseBody.status || + responseBody.verificationStatus || + responseBody.result || + (responseBody.verified === true ? 'verified' : undefined) || + (responseBody.valid === true ? 'verified' : undefined) || + (responseBody.match === true ? 'verified' : undefined) || + 'unknown' + ); +} + +function extractReceiptSignature(responseBody) { + if (typeof responseBody.receipt_signature === 'string') { + return responseBody.receipt_signature; + } + + if (typeof responseBody.receiptSignature === 'string') { + return responseBody.receiptSignature; + } + + if ( + responseBody.receiptSignature && + typeof responseBody.receiptSignature.signature === 'string' + ) { + return responseBody.receiptSignature.signature; + } + + return ''; +} + +function isVerificationValid(responseBody, status) { + if ([responseBody.valid, responseBody.verified, responseBody.match].includes(true)) { + return true; + } + + if ([responseBody.valid, responseBody.verified, responseBody.match].includes(false)) { + return false; + } + + const normalizedStatus = String(status || '').toLowerCase(); + if (['verified', 'valid', 'match', 'matched', 'success', 'ok'].includes(normalizedStatus)) { + return true; + } + + if (['invalid', 'mismatch', 'failed', 'error', 'tampered'].includes(normalizedStatus)) { + return false; + } + + return false; +} + +function extractMessage(responseBody) { + if (!responseBody || typeof responseBody !== 'object') { + return ''; + } + + return ( + responseBody.error || + responseBody.message || + responseBody.detail || + responseBody.title || + '' + ); +} + +async function parseJsonResponse(response) { + const rawBody = await response.text(); + if (!rawBody) { + return {}; + } + + try { + return JSON.parse(rawBody); + } catch { + throw new Error(`TrustSignal API returned a non-JSON response with status ${response.status}`); + } +} + +async function callVerificationApi({ apiBaseUrl, apiKey, artifactHash, artifactPath, source }) { + const endpoint = `${apiBaseUrl}/api/v1/verify`; + const payload = buildVerificationRequest({ artifactHash, artifactPath, source }); + + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-api-key': apiKey + }, + body: JSON.stringify(payload) + }); + + const responseBody = await parseJsonResponse(response); + + if (!response.ok) { + const message = extractMessage(responseBody); + throw new Error( + `TrustSignal API request failed with status ${response.status}${ + message ? `: ${message}` : '' + }` + ); + } + + return responseBody || {}; +} + +async function run() { + try { + const apiBaseUrl = normalizeBaseUrl(getInput('api_base_url', { required: true })); + const apiKey = getInput('api_key', { required: true }); + const artifactPath = getInput('artifact_path'); + const providedArtifactHash = getInput('artifact_hash'); + const source = getInput('source') || 'github-actions'; + const failOnMismatch = getBooleanInput('fail_on_mismatch', true); + + if (!artifactPath && !providedArtifactHash) { + throw new Error('Either artifact_path or artifact_hash must be provided'); + } + + if (artifactPath && providedArtifactHash) { + throw new Error('Provide only one of artifact_path or artifact_hash'); + } + + const artifactHash = artifactPath + ? sha256File(artifactPath) + : validateHash(providedArtifactHash); + + const responseBody = await callVerificationApi({ + apiBaseUrl, + apiKey, + artifactHash, + artifactPath, + source + }); + + const verificationId = + responseBody.verification_id || + responseBody.verificationId || + responseBody.id || + responseBody.receipt_id || + responseBody.receiptId || + ''; + const receiptId = responseBody.receipt_id || responseBody.receiptId || ''; + const status = deriveStatus(responseBody); + const receiptSignature = extractReceiptSignature(responseBody); + const isValid = isVerificationValid(responseBody, status); + + setOutput('verification_id', verificationId); + setOutput('status', status); + setOutput('receipt_id', receiptId); + setOutput('receipt_signature', receiptSignature); + + if (failOnMismatch && !isValid) { + throw new Error(`TrustSignal verification was not valid. Status: ${status}`); + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown action failure'; + setFailed(message); + } +} + +run(); From 011cd93ae23a0c7bd972ec6e1c45a7edf4fe181d Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 01:20:16 -0500 Subject: [PATCH 17/25] chore: track built action entrypoint --- .../trustsignal-verify-artifact/dist/index.js | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 github-actions/trustsignal-verify-artifact/dist/index.js diff --git a/github-actions/trustsignal-verify-artifact/dist/index.js b/github-actions/trustsignal-verify-artifact/dist/index.js new file mode 100644 index 0000000..05625ea --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/dist/index.js @@ -0,0 +1,281 @@ +const crypto = require('node:crypto'); +const fs = require('node:fs'); +const path = require('node:path'); + +function getInput(name, options = {}) { + const envName = `INPUT_${name.replace(/ /g, '_').toUpperCase()}`; + const raw = process.env[envName]; + const value = typeof raw === 'string' ? raw.trim() : ''; + + if (options.required && !value) { + throw new Error(`Missing required input: ${name}`); + } + + return value; +} + +function getBooleanInput(name, defaultValue = false) { + const value = getInput(name); + if (!value) return defaultValue; + + const normalized = value.toLowerCase(); + if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) return true; + if (['false', '0', 'no', 'n', 'off'].includes(normalized)) return false; + + throw new Error(`Invalid boolean input for ${name}: expected true or false`); +} + +function setOutput(name, value) { + const outputPath = process.env.GITHUB_OUTPUT; + if (!outputPath) { + process.stdout.write(`${name}=${value}\n`); + return; + } + + fs.appendFileSync(outputPath, `${name}=${String(value ?? '')}\n`, 'utf8'); +} + +function setFailed(message) { + process.stderr.write(`::error::${message}\n`); + process.exitCode = 1; +} + +function sha256File(filePath) { + const absolutePath = path.resolve(filePath); + if (!fs.existsSync(absolutePath)) { + throw new Error(`Artifact file not found: ${absolutePath}`); + } + + const stats = fs.statSync(absolutePath); + if (!stats.isFile()) { + throw new Error(`Artifact path is not a file: ${absolutePath}`); + } + + const hash = crypto.createHash('sha256'); + const fileBuffer = fs.readFileSync(absolutePath); + hash.update(fileBuffer); + return hash.digest('hex'); +} + +function validateHash(value) { + const normalized = value.toLowerCase().replace(/^sha256:/, ''); + if (!/^[a-f0-9]{64}$/.test(normalized)) { + throw new Error('artifact_hash must be a valid SHA-256 hex digest'); + } + return normalized; +} + +function normalizeBaseUrl(value) { + let url; + + try { + url = new URL(value); + } catch { + throw new Error('api_base_url must be a valid URL'); + } + + if (!/^https?:$/.test(url.protocol)) { + throw new Error('api_base_url must use http or https'); + } + + url.pathname = url.pathname.replace(/\/+$/, ''); + url.search = ''; + url.hash = ''; + return url.toString().replace(/\/$/, ''); +} + +function getGitHubContext() { + return { + repository: process.env.GITHUB_REPOSITORY || undefined, + runId: process.env.GITHUB_RUN_ID || undefined, + workflow: process.env.GITHUB_WORKFLOW || undefined, + actor: process.env.GITHUB_ACTOR || undefined, + sha: process.env.GITHUB_SHA || undefined + }; +} + +function buildVerificationRequest({ artifactHash, artifactPath, source }) { + const github = getGitHubContext(); + const provider = source.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 64) || 'github-actions'; + + return { + artifact: { + hash: artifactHash, + algorithm: 'sha256' + }, + source: { + provider, + repository: github.repository, + workflow: github.workflow, + runId: github.runId, + actor: github.actor, + commit: github.sha + }, + metadata: { + ...(artifactPath ? { artifactPath } : {}) + } + }; +} + +function deriveStatus(responseBody) { + return ( + responseBody.status || + responseBody.verificationStatus || + responseBody.result || + (responseBody.verified === true ? 'verified' : undefined) || + (responseBody.valid === true ? 'verified' : undefined) || + (responseBody.match === true ? 'verified' : undefined) || + 'unknown' + ); +} + +function extractReceiptSignature(responseBody) { + if (typeof responseBody.receipt_signature === 'string') { + return responseBody.receipt_signature; + } + + if (typeof responseBody.receiptSignature === 'string') { + return responseBody.receiptSignature; + } + + if ( + responseBody.receiptSignature && + typeof responseBody.receiptSignature.signature === 'string' + ) { + return responseBody.receiptSignature.signature; + } + + return ''; +} + +function isVerificationValid(responseBody, status) { + if ([responseBody.valid, responseBody.verified, responseBody.match].includes(true)) { + return true; + } + + if ([responseBody.valid, responseBody.verified, responseBody.match].includes(false)) { + return false; + } + + const normalizedStatus = String(status || '').toLowerCase(); + if (['verified', 'valid', 'match', 'matched', 'success', 'ok'].includes(normalizedStatus)) { + return true; + } + + if (['invalid', 'mismatch', 'failed', 'error', 'tampered'].includes(normalizedStatus)) { + return false; + } + + return false; +} + +function extractMessage(responseBody) { + if (!responseBody || typeof responseBody !== 'object') { + return ''; + } + + return ( + responseBody.error || + responseBody.message || + responseBody.detail || + responseBody.title || + '' + ); +} + +async function parseJsonResponse(response) { + const rawBody = await response.text(); + if (!rawBody) { + return {}; + } + + try { + return JSON.parse(rawBody); + } catch { + throw new Error(`TrustSignal API returned a non-JSON response with status ${response.status}`); + } +} + +async function callVerificationApi({ apiBaseUrl, apiKey, artifactHash, artifactPath, source }) { + const endpoint = `${apiBaseUrl}/api/v1/verify`; + const payload = buildVerificationRequest({ artifactHash, artifactPath, source }); + + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-api-key': apiKey + }, + body: JSON.stringify(payload) + }); + + const responseBody = await parseJsonResponse(response); + + if (!response.ok) { + const message = extractMessage(responseBody); + throw new Error( + `TrustSignal API request failed with status ${response.status}${ + message ? `: ${message}` : '' + }` + ); + } + + return responseBody || {}; +} + +async function run() { + try { + const apiBaseUrl = normalizeBaseUrl(getInput('api_base_url', { required: true })); + const apiKey = getInput('api_key', { required: true }); + const artifactPath = getInput('artifact_path'); + const providedArtifactHash = getInput('artifact_hash'); + const source = getInput('source') || 'github-actions'; + const failOnMismatch = getBooleanInput('fail_on_mismatch', true); + + if (!artifactPath && !providedArtifactHash) { + throw new Error('Either artifact_path or artifact_hash must be provided'); + } + + if (artifactPath && providedArtifactHash) { + throw new Error('Provide only one of artifact_path or artifact_hash'); + } + + const artifactHash = artifactPath + ? sha256File(artifactPath) + : validateHash(providedArtifactHash); + + const responseBody = await callVerificationApi({ + apiBaseUrl, + apiKey, + artifactHash, + artifactPath, + source + }); + + const verificationId = + responseBody.verification_id || + responseBody.verificationId || + responseBody.id || + responseBody.receipt_id || + responseBody.receiptId || + ''; + const receiptId = responseBody.receipt_id || responseBody.receiptId || ''; + const status = deriveStatus(responseBody); + const receiptSignature = extractReceiptSignature(responseBody); + const isValid = isVerificationValid(responseBody, status); + + setOutput('verification_id', verificationId); + setOutput('status', status); + setOutput('receipt_id', receiptId); + setOutput('receipt_signature', receiptSignature); + + if (failOnMismatch && !isValid) { + throw new Error(`TrustSignal verification was not valid. Status: ${status}`); + } + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown action failure'; + setFailed(message); + } +} + +run(); From 36c9aa955c402c7faa7552a293f6045bbfa933d0 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 01:49:02 -0500 Subject: [PATCH 18/25] feat: finalize github action repo and polish for marketplace readiness --- .../trustsignal-verify-artifact/.gitignore | 7 + .../CONTRIBUTING.md | 34 ++++ .../trustsignal-verify-artifact/LICENSE | 21 ++ .../trustsignal-verify-artifact/README.md | 180 +++++++++++------- .../trustsignal-verify-artifact/SECURITY.md | 23 +++ .../docs/integration.md | 55 ++++++ .../trustsignal-verify-artifact/package.json | 8 +- 7 files changed, 258 insertions(+), 70 deletions(-) create mode 100644 github-actions/trustsignal-verify-artifact/.gitignore create mode 100644 github-actions/trustsignal-verify-artifact/CONTRIBUTING.md create mode 100644 github-actions/trustsignal-verify-artifact/LICENSE create mode 100644 github-actions/trustsignal-verify-artifact/SECURITY.md create mode 100644 github-actions/trustsignal-verify-artifact/docs/integration.md diff --git a/github-actions/trustsignal-verify-artifact/.gitignore b/github-actions/trustsignal-verify-artifact/.gitignore new file mode 100644 index 0000000..fe03786 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.DS_Store +coverage/ +*.log +tmp/ +!dist/ +!dist/index.js diff --git a/github-actions/trustsignal-verify-artifact/CONTRIBUTING.md b/github-actions/trustsignal-verify-artifact/CONTRIBUTING.md new file mode 100644 index 0000000..7eddfd9 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing + +## Local Validation + +Run the lightweight validation checks before opening a change: + +```bash +node --check src/index.js +node --check dist/index.js +node scripts/test-local.js +``` + +Or use package scripts: + +```bash +npm run check +npm run test:local +npm run validate:local +``` + +## Repository Structure + +- `action.yml`: GitHub Action metadata +- `src/`: source implementation +- `dist/`: committed runtime entrypoint for action consumers +- `scripts/`: local validation helpers +- `docs/`: integration-facing documentation + +## Release Basics + +- Follow semantic versioning. +- Commit updated `dist/index.js` with each release. +- Publish immutable tags such as `v0.1.0` and maintain a major tag such as `v1`. +- GitHub Marketplace publication requires a public repository with `action.yml` at the repository root. diff --git a/github-actions/trustsignal-verify-artifact/LICENSE b/github-actions/trustsignal-verify-artifact/LICENSE new file mode 100644 index 0000000..bdbdc1d --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 TrustSignal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/github-actions/trustsignal-verify-artifact/README.md b/github-actions/trustsignal-verify-artifact/README.md index e946a18..9c0e0a5 100644 --- a/github-actions/trustsignal-verify-artifact/README.md +++ b/github-actions/trustsignal-verify-artifact/README.md @@ -1,26 +1,46 @@ # TrustSignal Verify Artifact -`TrustSignal Verify Artifact` is a JavaScript GitHub Action for generic build artifact verification. It hashes a build artifact or accepts a precomputed SHA-256 digest, submits that identity to TrustSignal, and returns signed receipt metadata to the workflow. +Verify build artifacts in CI, issue signed verification receipts, and preserve provenance for later verification and audit workflows. -TrustSignal issues signed verification receipts for the artifact identity you submit. Store the returned `receipt_id` if you want to support later verification flows in downstream systems. +[![License: MIT](https://img.shields.io/badge/license-MIT-informational)](LICENSE) +[![Node.js](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](package.json) -## What This Action Does +`TrustSignal Verify Artifact` is a JavaScript GitHub Action for teams that want a clean verification checkpoint inside CI/CD. It hashes a build artifact or accepts a precomputed SHA-256 digest, submits that identity to TrustSignal, and returns receipt metadata your pipeline can persist, attach to release records, or feed into later verification workflows. -- Verifies build artifacts with a generic TrustSignal artifact verification contract. -- Accepts either a local artifact path or a precomputed SHA-256 hash. -- Adds GitHub workflow context automatically when it is available in the runtime environment. -- Returns receipt metadata that can be persisted for later verification. +TrustSignal is built for artifact integrity, signed receipts, verifiable provenance, and downstream auditability without exposing internal verification engine details. + +## Features + +- Verify build artifacts directly inside GitHub Actions +- Issue signed verification receipts for CI outputs +- Preserve provenance context from the GitHub workflow runtime +- Support later verification and audit workflows through `receipt_id` +- Fail closed on invalid or mismatched verification results when required + +## Why Teams Use It + +- Add a lightweight integrity control to release workflows +- Preserve a verifiable record of what was checked in CI +- Improve traceability across build, release, and audit paths +- Standardize artifact verification without embedding internal platform logic in workflows + +## Quick Start + +1. Add a TrustSignal API key to GitHub Actions secrets. +2. Call the action with either `artifact_path` or `artifact_hash`. +3. Capture `receipt_id` and `receipt_signature` in downstream steps. +4. Store receipt metadata anywhere you need later verification or audit evidence. ## Inputs | Input | Required | Description | | --- | --- | --- | -| `api_base_url` | Yes | Base URL for the TrustSignal public API, such as `https://api.trustsignal.dev`. | -| `api_key` | Yes | API key for the TrustSignal public verification endpoint. Store this in GitHub Actions secrets. | +| `api_base_url` | Yes | Base URL for the TrustSignal public API, for example `https://api.trustsignal.dev`. | +| `api_key` | Yes | TrustSignal API key. Pass it from GitHub Actions secrets. | | `artifact_path` | No | Local path to the artifact file to hash with SHA-256. | | `artifact_hash` | No | Precomputed SHA-256 digest to verify instead of hashing a local file. | -| `source` | No | Source provider label attached to the verification request. Defaults to `github-actions`. | -| `fail_on_mismatch` | No | When `true`, fails the workflow if TrustSignal does not return a valid verification result. Defaults to `true`. | +| `source` | No | Source provider label sent in the verification request. Defaults to `github-actions`. | +| `fail_on_mismatch` | No | When `true`, the action fails on non-valid verification results. Defaults to `true`. | Provide exactly one of `artifact_path` or `artifact_hash`. @@ -28,42 +48,14 @@ Provide exactly one of `artifact_path` or `artifact_hash`. | Output | Description | | --- | --- | -| `verification_id` | Verification identifier returned by TrustSignal. | -| `status` | Verification status normalized from the TrustSignal API response. | -| `receipt_id` | Receipt identifier returned by TrustSignal. | -| `receipt_signature` | Receipt signature returned by TrustSignal. | - -Compatibility note: - -- `verification_id` falls back to `receipt_id` when the API does not return a distinct verification identifier. - -## Request Contract - -The action sends `POST /api/v1/verify` with this body shape: - -```json -{ - "artifact": { - "hash": "", - "algorithm": "sha256" - }, - "source": { - "provider": "github-actions", - "repository": "", - "workflow": "", - "runId": "", - "commit": "", - "actor": "" - }, - "metadata": { - "artifactPath": "" - } -} -``` +| `verification_id` | Verification identifier returned by TrustSignal. Falls back to `receipt_id` when the API does not return a separate value. | +| `status` | Normalized verification status returned by the API. | +| `receipt_id` | Signed receipt identifier returned by TrustSignal. | +| `receipt_signature` | Signed receipt signature returned by TrustSignal. | -The action adds `source.repository`, `source.workflow`, `source.runId`, `source.commit`, and `source.actor` automatically from the GitHub Actions runtime when those values are available. +## Example Usage -## Example Workflow Using `artifact_path` +### Verify An Artifact File ```yaml name: Verify Build Artifact @@ -97,29 +89,30 @@ jobs: source: github-actions fail_on_mismatch: "true" - - name: Print receipt metadata + - name: Record verification outputs run: | - echo "Verification: ${{ steps.trustsignal.outputs.verification_id }}" + echo "Verification ID: ${{ steps.trustsignal.outputs.verification_id }}" echo "Status: ${{ steps.trustsignal.outputs.status }}" - echo "Receipt: ${{ steps.trustsignal.outputs.receipt_id }}" + echo "Receipt ID: ${{ steps.trustsignal.outputs.receipt_id }}" + echo "Receipt Signature: ${{ steps.trustsignal.outputs.receipt_signature }}" ``` -## Example Workflow Using `artifact_hash` +### Verify A Precomputed Hash ```yaml -name: Verify Precomputed Hash +name: Verify Artifact Hash on: workflow_dispatch: jobs: - verify-artifact-hash: + verify-hash: runs-on: ubuntu-latest permissions: contents: read steps: - - name: Verify known artifact digest + - name: Verify known digest id: trustsignal uses: trustsignal-dev/trustsignal-verify-artifact@v1 with: @@ -129,31 +122,59 @@ jobs: source: github-actions fail_on_mismatch: "true" - - name: Print result + - name: Print verification result run: | - echo "Verification: ${{ steps.trustsignal.outputs.verification_id }}" + echo "Verification ID: ${{ steps.trustsignal.outputs.verification_id }}" echo "Status: ${{ steps.trustsignal.outputs.status }}" ``` -Later verification is possible by storing the returned `receipt_id` and using it with TrustSignal APIs that expose receipt lookup or verification. +## Request Contract -## Security Notes +The action calls `POST /api/v1/verify` with a generic artifact verification payload: + +```json +{ + "artifact": { + "hash": "", + "algorithm": "sha256" + }, + "source": { + "provider": "github-actions", + "repository": "", + "workflow": "", + "runId": "", + "commit": "", + "actor": "" + }, + "metadata": { + "artifactPath": "" + } +} +``` + +GitHub workflow context is added automatically when those environment variables are available at runtime. + +## Security Considerations -- The action never logs the API key. - The API key is sent only in the `x-api-key` header. -- API failures are surfaced with concise HTTP status messages and without raw internal details. -- Inputs are validated before any request is sent. +- The action does not log secrets. +- Error messages are concise and avoid raw internal details. - Local artifact hashing uses SHA-256 from Node.js `crypto`. +- `fail_on_mismatch` allows pipelines to enforce fail-closed verification behavior. + +## Why TrustSignal + +TrustSignal helps teams add a verification layer to CI/CD without exposing proprietary implementation details in every workflow. The action focuses on artifact identity, signed receipts, provenance continuity, and later verification so integrity signals can travel with the software lifecycle. ## Current Limitations -- This package is an integration scaffold and assumes a TrustSignal deployment that supports the generic artifact verification contract documented here. -- A live deployed API integration test is still pending; the included local test uses a fetch mock that validates the contract shape and action behavior. -- This package currently lives under `github-actions/trustsignal-verify-artifact/` inside the TrustSignal monorepo, so it is not yet directly publishable to GitHub Marketplace. +- The local test path uses a fetch mock rather than a live TrustSignal deployment. +- This package is extractable today, but Marketplace publication still requires a dedicated public repository root. +- A production-facing integration test against a deployed TrustSignal API is still pending. ## Local Validation -Run the local syntax checks and contract test with: +Run the lightweight validation checks with: ```bash node --check src/index.js @@ -169,9 +190,32 @@ npm run test:local npm run validate:local ``` -## Marketplace Readiness Status +## Versioning Guidance + +- Follow semantic versioning. +- Publish immutable release tags such as `v0.1.0`, `v0.2.0`, and `v1.0.0`. +- Maintain a major tag such as `v1` for stable consumers. + +## Release Guidance + +- Commit the built `dist/index.js` artifact with every release. +- Create signed or otherwise controlled release tags according to your release process. +- Update documentation when the public API contract or output mapping changes. +- Marketplace publication requires a public repository, a root-level `action.yml`, and release tags. + +## Roadmap + +- Add a live integration test against a deployed TrustSignal verification endpoint +- Publish the action from a dedicated public repository root +- Add example workflows for release pipelines and provenance retention patterns + +## Suggested GitHub Topics -- Not Marketplace-ready while it lives under `github-actions/trustsignal-verify-artifact/` inside the TrustSignal monorepo. -- Marketplace publication requires a dedicated public repository with `action.yml` at the repository root. -- Marketplace publication also requires release tagging, committed `dist/index.js` artifacts, and final public repository metadata. -- Update the README and integration doc when the public TrustSignal API contract or output fields change. +- `github-action` +- `devsecops` +- `ci-cd` +- `artifact-integrity` +- `supply-chain-security` +- `compliance` +- `provenance` +- `verification` diff --git a/github-actions/trustsignal-verify-artifact/SECURITY.md b/github-actions/trustsignal-verify-artifact/SECURITY.md new file mode 100644 index 0000000..c351639 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Reporting A Vulnerability + +Report suspected vulnerabilities privately to `security@trustsignal.dev`. + +Include: + +- a clear description of the issue +- reproduction steps +- affected versions or commit references +- impact assessment if known + +Do not open public GitHub issues for suspected security vulnerabilities. + +## Sensitive Information + +- Do not include secrets, API keys, tokens, customer data, or private receipts in reports. +- Sanitize logs, payloads, and screenshots before sharing them. + +## Responsible Disclosure + +TrustSignal reviews reports as quickly as possible, validates impact, and coordinates remediation and disclosure timing with reporters when appropriate. diff --git a/github-actions/trustsignal-verify-artifact/docs/integration.md b/github-actions/trustsignal-verify-artifact/docs/integration.md new file mode 100644 index 0000000..9483297 --- /dev/null +++ b/github-actions/trustsignal-verify-artifact/docs/integration.md @@ -0,0 +1,55 @@ +# Integration Guide + +## Overview + +`TrustSignal Verify Artifact` verifies build artifacts in CI, issues signed verification receipts, and returns receipt metadata that downstream systems can use for provenance and later verification workflows. + +## Verification Flow + +1. The action accepts either `artifact_path` or `artifact_hash`. +2. A SHA-256 digest is computed locally when a file path is provided. +3. The action sends the artifact identity and GitHub workflow context to `POST /api/v1/verify`. +4. TrustSignal returns verification metadata, including a receipt identifier and receipt signature. +5. The workflow stores `receipt_id` for later verification, audit, or provenance workflows. + +## Request Contract + +```json +{ + "artifact": { + "hash": "", + "algorithm": "sha256" + }, + "source": { + "provider": "github-actions", + "repository": "", + "workflow": "", + "runId": "", + "commit": "", + "actor": "" + }, + "metadata": { + "artifactPath": "" + } +} +``` + +## Outputs + +- `verification_id` +- `status` +- `receipt_id` +- `receipt_signature` + +If the API omits a distinct verification identifier, the action uses `receipt_id` as a compatibility alias for `verification_id`. + +## Current Limitations + +- The included test path uses a local fetch mock rather than a live TrustSignal deployment. +- Marketplace publication still requires extraction into a dedicated public repository. + +## Next Steps + +- Add a live integration test against a deployed TrustSignal API environment. +- Publish semantic version tags and maintain a stable major tag. +- Move this package to the repository root of a dedicated public action repository. diff --git a/github-actions/trustsignal-verify-artifact/package.json b/github-actions/trustsignal-verify-artifact/package.json index 14596c4..f2b1d7e 100644 --- a/github-actions/trustsignal-verify-artifact/package.json +++ b/github-actions/trustsignal-verify-artifact/package.json @@ -1,7 +1,7 @@ { "name": "trustsignal-verify-artifact", "version": "0.1.0", - "description": "GitHub Action that issues and verifies TrustSignal receipts for build artifacts.", + "description": "GitHub Action for verifying build artifacts with TrustSignal and capturing signed verification receipts.", "main": "dist/index.js", "type": "commonjs", "files": [ @@ -21,8 +21,12 @@ "github-action", "trustsignal", "verification", + "devsecops", + "ci-cd", "supply-chain", - "artifact" + "artifact", + "provenance", + "compliance" ], "author": "TrustSignal", "license": "MIT", From a2431afb3770c5bb4c55c4a628149f9b91417ee9 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 02:31:49 -0500 Subject: [PATCH 19/25] feat: add secure supabase-backed artifact verification api for github action --- .env.example | 3 + apps/api/.env.example | 3 + apps/api/package.json | 1 + .../migration.sql | 51 + apps/api/prisma/schema.prisma | 21 + apps/api/src/artifact-verification.test.ts | 116 +++ apps/api/src/artifactReceipts.ts | 299 ++++++ apps/api/src/db.ts | 58 ++ apps/api/src/request-validation.test.ts | 32 +- apps/api/src/server.ts | 891 ++++-------------- apps/api/src/supabaseAdmin.ts | 40 + docs/integrations/github-action.md | 122 +-- docs/security-summary.md | 4 + openapi.yaml | 179 +++- 14 files changed, 985 insertions(+), 835 deletions(-) create mode 100644 apps/api/prisma/migrations/20260313090000_add_artifact_receipts/migration.sql create mode 100644 apps/api/src/artifact-verification.test.ts create mode 100644 apps/api/src/artifactReceipts.ts create mode 100644 apps/api/src/supabaseAdmin.ts diff --git a/.env.example b/.env.example index 1dbf981..bf0c3df 100644 --- a/.env.example +++ b/.env.example @@ -5,11 +5,14 @@ ISSUER_PUBLIC_JWK_JSON='{"kty":"EC","crv":"P-256","x":"","y":""}' DB_PATH=attestations.sqlite DATABASE_URL="file:./prisma/dev.db" # Supabase aliases for apps/api Postgres (optional) +SUPABASE_URL=https://.supabase.co +SUPABASE_SERVICE_ROLE_KEY=replace-with-server-only-service-role-key SUPABASE_DB_URL=postgresql://postgres.:[password]@aws-0-.pooler.supabase.com:6543/postgres?sslmode=require SUPABASE_POOLER_URL=postgresql://postgres.:[password]@aws-0-.pooler.supabase.com:6543/postgres?sslmode=require SUPABASE_DIRECT_URL=postgresql://postgres:[password]@db..supabase.co:5432/postgres?sslmode=require # Optional helper if using Supabase CLI pooler URL discovery from `supabase/.temp/pooler-url`. SUPABASE_DB_PASSWORD=replace-with-supabase-db-password +SUPABASE_SECRET_KEY=replace-with-server-only-secret-key PORT=3000 # apps/api security controls diff --git a/apps/api/.env.example b/apps/api/.env.example index c3692b5..b1a7508 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -42,11 +42,14 @@ RATE_LIMIT_API_KEY_MAX=120 # Database (must enforce TLS; include sslmode=require) DATABASE_URL=postgresql://user:password@host:5432/deedshield?sslmode=require # Supabase aliases (optional if you prefer naming by provider) +SUPABASE_URL=https://.supabase.co +SUPABASE_SERVICE_ROLE_KEY=replace-with-server-only-service-role-key SUPABASE_DB_URL=postgresql://postgres.:[password]@aws-0-.pooler.supabase.com:6543/postgres?sslmode=require SUPABASE_POOLER_URL=postgresql://postgres.:[password]@aws-0-.pooler.supabase.com:6543/postgres?sslmode=require SUPABASE_DIRECT_URL=postgresql://postgres:[password]@db..supabase.co:5432/postgres?sslmode=require # Optional helper if using Supabase CLI pooler URL discovery from `supabase/.temp/pooler-url`. SUPABASE_DB_PASSWORD=replace-with-supabase-db-password +SUPABASE_SECRET_KEY=replace-with-server-only-secret-key # Blockchain Configuration (Sepolia or Local) ANCHOR_REGISTRY_ADDRESS=0x... diff --git a/apps/api/package.json b/apps/api/package.json index 3af10a7..ff1e038 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,6 +21,7 @@ "@prisma/client": "^5.17.0", "ethers": "^6.12.0", "fastify": "^5.8.1", + "jose": "^5.2.4", "openai": "^6.17.0", "pdf2json": "^3.1.4", "pdfkit": "^0.15.0", diff --git a/apps/api/prisma/migrations/20260313090000_add_artifact_receipts/migration.sql b/apps/api/prisma/migrations/20260313090000_add_artifact_receipts/migration.sql new file mode 100644 index 0000000..b24d7ed --- /dev/null +++ b/apps/api/prisma/migrations/20260313090000_add_artifact_receipts/migration.sql @@ -0,0 +1,51 @@ +CREATE TABLE "ArtifactReceipt" ( + "receiptId" TEXT NOT NULL PRIMARY KEY, + "verificationId" TEXT NOT NULL, + "artifactHash" TEXT NOT NULL, + "algorithm" TEXT NOT NULL, + "sourceProvider" TEXT NOT NULL, + "repository" TEXT, + "workflow" TEXT, + "runId" TEXT, + "commitSha" TEXT, + "actor" TEXT, + "status" TEXT NOT NULL, + "receiptSignature" TEXT NOT NULL, + "receiptSignatureAlg" TEXT NOT NULL, + "receiptSignatureKid" TEXT NOT NULL, + "metadataArtifactPath" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX "ArtifactReceipt_verificationId_key" +ON "ArtifactReceipt" ("verificationId"); + +CREATE INDEX "ArtifactReceipt_createdAt_idx" +ON "ArtifactReceipt" ("createdAt"); + +ALTER TABLE "ArtifactReceipt" ENABLE ROW LEVEL SECURITY; +ALTER TABLE "ArtifactReceipt" FORCE ROW LEVEL SECURITY; + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'postgres') THEN + CREATE POLICY "artifact_receipts_postgres_all" + ON "ArtifactReceipt" + FOR ALL + TO postgres + USING (true) + WITH CHECK (true); + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN + CREATE POLICY "artifact_receipts_service_role_all" + ON "ArtifactReceipt" + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + END IF; +END $$; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 1b82054..e81741c 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -34,6 +34,27 @@ model Receipt { revoked Boolean @default(false) } +model ArtifactReceipt { + receiptId String @id + verificationId String @unique + artifactHash String + algorithm String + sourceProvider String + repository String? + workflow String? + runId String? + commitSha String? + actor String? + status String + receiptSignature String + receiptSignatureAlg String + receiptSignatureKid String + metadataArtifactPath String? + createdAt DateTime @default(now()) + + @@index([createdAt]) +} + model Property { parcelId String @id currentOwner String diff --git a/apps/api/src/artifact-verification.test.ts b/apps/api/src/artifact-verification.test.ts new file mode 100644 index 0000000..9fea071 --- /dev/null +++ b/apps/api/src/artifact-verification.test.ts @@ -0,0 +1,116 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { FastifyInstance } from 'fastify'; +import { PrismaClient } from '@prisma/client'; + +import { buildServer } from './server.js'; + +const hasDatabaseUrl = + Boolean(process.env.DATABASE_URL) || + Boolean(process.env.SUPABASE_DB_URL) || + Boolean(process.env.SUPABASE_POOLER_URL) || + Boolean(process.env.SUPABASE_DIRECT_URL); +const describeWithDatabase = hasDatabaseUrl ? describe.sequential : describe.skip; + +describeWithDatabase('Generic artifact verification API', () => { + let app: FastifyInstance; + let prisma: PrismaClient; + const apiKey = 'artifact-test-api-key'; + + beforeAll(async () => { + process.env.API_KEYS = apiKey; + process.env.API_KEY_SCOPES = `${apiKey}=verify|read`; + prisma = new PrismaClient(); + app = await buildServer(); + }); + + afterAll(async () => { + await app.close(); + await prisma.$disconnect(); + delete process.env.API_KEYS; + delete process.env.API_KEY_SCOPES; + }); + + it('issues, persists, and later verifies a generic artifact receipt', async () => { + const artifactHash = + '2f77668a9dfbf8d5847cf2d5d0370740e0c0601b4f061c1181f58c77c2b8f486'; + + const verifyRes = await app.inject({ + method: 'POST', + url: '/api/v1/verify', + headers: { 'x-api-key': apiKey }, + payload: { + artifact: { + hash: artifactHash, + algorithm: 'sha256' + }, + source: { + provider: 'github-actions', + repository: 'TrustSignal-dev/TrustSignal-Verify-Artifact', + workflow: 'Verify Build Artifact', + runId: '12345', + commit: 'abc123def456', + actor: 'octocat' + }, + metadata: { + artifactPath: 'dist/release.txt' + } + } + }); + + expect(verifyRes.statusCode).toBe(200); + const receipt = verifyRes.json(); + expect(receipt.status).toBe('verified'); + expect(receipt.receiptId).toBeTruthy(); + expect(receipt.verificationId).toBe(receipt.receiptId); + expect(typeof receipt.receiptSignature).toBe('string'); + + const persistedRows = await prisma.$queryRawUnsafe>( + `SELECT "receiptId" FROM "ArtifactReceipt" WHERE "receiptId" = $1`, + receipt.receiptId + ); + expect(persistedRows).toHaveLength(1); + + const laterVerifyMatch = await app.inject({ + method: 'POST', + url: `/api/v1/receipt/${receipt.receiptId}/verify`, + headers: { 'x-api-key': apiKey }, + payload: { + artifact: { + hash: artifactHash, + algorithm: 'sha256' + } + } + }); + + expect(laterVerifyMatch.statusCode).toBe(200); + expect(laterVerifyMatch.json()).toMatchObject({ + verified: true, + integrityVerified: true, + signatureVerified: true, + status: 'verified', + receiptId: receipt.receiptId, + storedHash: artifactHash, + recomputedHash: artifactHash + }); + + const laterVerifyMismatch = await app.inject({ + method: 'POST', + url: `/api/v1/receipt/${receipt.receiptId}/verify`, + headers: { 'x-api-key': apiKey }, + payload: { + artifact: { + hash: '1111111111111111111111111111111111111111111111111111111111111111', + algorithm: 'sha256' + } + } + }); + + expect(laterVerifyMismatch.statusCode).toBe(200); + expect(laterVerifyMismatch.json()).toMatchObject({ + verified: false, + integrityVerified: false, + status: 'mismatch', + receiptId: receipt.receiptId + }); + }); +}); diff --git a/apps/api/src/artifactReceipts.ts b/apps/api/src/artifactReceipts.ts new file mode 100644 index 0000000..760c397 --- /dev/null +++ b/apps/api/src/artifactReceipts.ts @@ -0,0 +1,299 @@ +import { randomUUID } from 'node:crypto'; + +import type { PrismaClient } from '@prisma/client'; +import { CompactSign, compactVerify, decodeProtectedHeader, importJWK } from 'jose'; +import type { SecurityConfig } from './security.js'; + +export type ArtifactVerificationRequest = { + artifact: { + hash: string; + algorithm: 'sha256'; + }; + source: { + provider: string; + repository?: string; + workflow?: string; + runId?: string; + commit?: string; + actor?: string; + }; + metadata?: { + artifactPath?: string; + }; +}; + +type ArtifactReceiptRow = { + receiptId: string; + verificationId: string; + artifactHash: string; + algorithm: string; + sourceProvider: string; + repository: string | null; + workflow: string | null; + runId: string | null; + commitSha: string | null; + actor: string | null; + status: string; + receiptSignature: string; + receiptSignatureAlg: string; + receiptSignatureKid: string; + metadataArtifactPath: string | null; + createdAt: Date; +}; + +type SignedArtifactReceiptPayload = { + receiptVersion: '1.0'; + receiptId: string; + verificationId: string; + createdAt: string; + artifact: { + hash: string; + algorithm: 'sha256'; + }; + source: { + provider: string; + repository?: string; + workflow?: string; + runId?: string; + commit?: string; + actor?: string; + }; + metadata?: { + artifactPath?: string; + }; + status: string; +}; + +function canonicalizeValue(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map((entry) => canonicalizeValue(entry)); + } + + if (value && typeof value === 'object') { + return Object.keys(value as Record) + .sort() + .reduce>((accumulator, key) => { + const nextValue = (value as Record)[key]; + if (typeof nextValue !== 'undefined') { + accumulator[key] = canonicalizeValue(nextValue); + } + return accumulator; + }, {}); + } + + return value; +} + +function canonicalizeArtifactPayload(payload: SignedArtifactReceiptPayload): string { + return JSON.stringify(canonicalizeValue(payload)); +} + +async function signArtifactReceiptPayload( + payload: SignedArtifactReceiptPayload, + securityConfig: SecurityConfig +) { + const signer = securityConfig.receiptSigning.current; + const key = await importJWK(signer.privateJwk, signer.alg); + const signature = await new CompactSign( + new TextEncoder().encode(canonicalizeArtifactPayload(payload)) + ) + .setProtectedHeader({ alg: signer.alg, kid: signer.kid, typ: 'receipt+jws' }) + .sign(key); + + return { + signature, + alg: signer.alg, + kid: signer.kid + }; +} + +async function verifyArtifactReceiptSignature( + payload: SignedArtifactReceiptPayload, + signature: { + signature: string; + alg: 'EdDSA'; + kid: string; + }, + securityConfig: SecurityConfig +) { + try { + const header = decodeProtectedHeader(signature.signature); + const kid = typeof header.kid === 'string' ? header.kid : signature.kid; + const alg = typeof header.alg === 'string' ? header.alg : signature.alg; + const publicJwk = securityConfig.receiptSigning.verificationKeys.get(kid); + if (!publicJwk) { + return false; + } + + const key = await importJWK(publicJwk, alg); + const { payload: verifiedPayload, protectedHeader } = await compactVerify(signature.signature, key); + const payloadString = new TextDecoder().decode(verifiedPayload); + return ( + payloadString === canonicalizeArtifactPayload(payload) && + protectedHeader.alg === signature.alg && + protectedHeader.kid === signature.kid + ); + } catch { + return false; + } +} + +function toSignedPayload(row: ArtifactReceiptRow): SignedArtifactReceiptPayload { + return { + receiptVersion: '1.0', + receiptId: row.receiptId, + verificationId: row.verificationId, + createdAt: row.createdAt.toISOString(), + artifact: { + hash: row.artifactHash, + algorithm: 'sha256' + }, + source: { + provider: row.sourceProvider, + ...(row.repository ? { repository: row.repository } : {}), + ...(row.workflow ? { workflow: row.workflow } : {}), + ...(row.runId ? { runId: row.runId } : {}), + ...(row.commitSha ? { commit: row.commitSha } : {}), + ...(row.actor ? { actor: row.actor } : {}) + }, + metadata: row.metadataArtifactPath + ? { artifactPath: row.metadataArtifactPath } + : undefined, + status: row.status + }; +} + +export async function issueArtifactReceipt( + prisma: PrismaClient, + securityConfig: SecurityConfig, + input: ArtifactVerificationRequest +) { + const receiptId = randomUUID(); + const verificationId = receiptId; + const createdAt = new Date(); + const status = 'verified'; + const unsignedPayload: SignedArtifactReceiptPayload = { + receiptVersion: '1.0', + receiptId, + verificationId, + createdAt: createdAt.toISOString(), + artifact: input.artifact, + source: input.source, + ...(input.metadata?.artifactPath ? { metadata: { artifactPath: input.metadata.artifactPath } } : {}), + status + }; + + const receiptSignature = await signArtifactReceiptPayload(unsignedPayload, securityConfig); + + await prisma.$executeRawUnsafe( + `INSERT INTO "ArtifactReceipt" ( + "receiptId", + "verificationId", + "artifactHash", + "algorithm", + "sourceProvider", + "repository", + "workflow", + "runId", + "commitSha", + "actor", + "status", + "receiptSignature", + "receiptSignatureAlg", + "receiptSignatureKid", + "metadataArtifactPath", + "createdAt" + ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`, + receiptId, + verificationId, + input.artifact.hash, + input.artifact.algorithm, + input.source.provider, + input.source.repository || null, + input.source.workflow || null, + input.source.runId || null, + input.source.commit || null, + input.source.actor || null, + status, + receiptSignature.signature, + receiptSignature.alg, + receiptSignature.kid, + input.metadata?.artifactPath || null, + createdAt + ); + + return { + verificationId, + receiptId, + receiptSignature: receiptSignature.signature, + status + }; +} + +export async function getArtifactReceiptById( + prisma: PrismaClient, + receiptId: string +): Promise { + const rows = await prisma.$queryRawUnsafe( + `SELECT + "receiptId", + "verificationId", + "artifactHash", + "algorithm", + "sourceProvider", + "repository", + "workflow", + "runId", + "commitSha", + "actor", + "status", + "receiptSignature", + "receiptSignatureAlg", + "receiptSignatureKid", + "metadataArtifactPath", + "createdAt" + FROM "ArtifactReceipt" + WHERE "receiptId" = $1 + LIMIT 1`, + receiptId + ); + + return rows[0] || null; +} + +export async function verifyArtifactReceiptById( + prisma: PrismaClient, + securityConfig: SecurityConfig, + receiptId: string, + artifact: { hash: string; algorithm: 'sha256' } +) { + const row = await getArtifactReceiptById(prisma, receiptId); + if (!row) return null; + + const unsignedPayload = toSignedPayload(row); + const signatureVerified = await verifyArtifactReceiptSignature( + unsignedPayload, + { + signature: row.receiptSignature, + alg: row.receiptSignatureAlg as 'EdDSA', + kid: row.receiptSignatureKid + }, + securityConfig + ); + + const integrityVerified = + row.algorithm === artifact.algorithm && + row.artifactHash === artifact.hash; + const verified = integrityVerified && signatureVerified; + + return { + verified, + integrityVerified, + signatureVerified, + status: verified ? row.status : 'mismatch', + receiptId: row.receiptId, + receiptSignature: row.receiptSignature, + storedHash: row.artifactHash, + recomputedHash: artifact.hash + }; +} diff --git a/apps/api/src/db.ts b/apps/api/src/db.ts index ab8dbf1..5f899c2 100644 --- a/apps/api/src/db.ts +++ b/apps/api/src/db.ts @@ -34,6 +34,64 @@ export async function ensureDatabase(prisma: PrismaClient) { `ALTER TABLE "Receipt" ADD COLUMN IF NOT EXISTS "receiptSignature" TEXT`, `ALTER TABLE "Receipt" ADD COLUMN IF NOT EXISTS "receiptSignatureAlg" TEXT`, `ALTER TABLE "Receipt" ADD COLUMN IF NOT EXISTS "receiptSignatureKid" TEXT`, + `CREATE TABLE IF NOT EXISTS "ArtifactReceipt" ( + "receiptId" TEXT PRIMARY KEY, + "verificationId" TEXT NOT NULL, + "artifactHash" TEXT NOT NULL, + "algorithm" TEXT NOT NULL, + "sourceProvider" TEXT NOT NULL, + "repository" TEXT, + "workflow" TEXT, + "runId" TEXT, + "commitSha" TEXT, + "actor" TEXT, + "status" TEXT NOT NULL, + "receiptSignature" TEXT NOT NULL, + "receiptSignatureAlg" TEXT NOT NULL, + "receiptSignatureKid" TEXT NOT NULL, + "metadataArtifactPath" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS "ArtifactReceipt_verificationId_key" + ON "ArtifactReceipt" ("verificationId")`, + `CREATE INDEX IF NOT EXISTS "ArtifactReceipt_createdAt_idx" + ON "ArtifactReceipt" ("createdAt")`, + `ALTER TABLE "ArtifactReceipt" ENABLE ROW LEVEL SECURITY`, + `ALTER TABLE "ArtifactReceipt" FORCE ROW LEVEL SECURITY`, + `DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_policies + WHERE schemaname = 'public' + AND tablename = 'ArtifactReceipt' + AND policyname = 'artifact_receipts_postgres_all' + ) THEN + CREATE POLICY "artifact_receipts_postgres_all" + ON "ArtifactReceipt" + FOR ALL + TO postgres + USING (true) + WITH CHECK (true); + END IF; + END $$`, + `DO $$ + BEGIN + IF EXISTS ( + SELECT 1 FROM pg_roles WHERE rolname = 'service_role' + ) AND NOT EXISTS ( + SELECT 1 FROM pg_policies + WHERE schemaname = 'public' + AND tablename = 'ArtifactReceipt' + AND policyname = 'artifact_receipts_service_role_all' + ) THEN + CREATE POLICY "artifact_receipts_service_role_all" + ON "ArtifactReceipt" + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + END IF; + END $$`, `CREATE TABLE IF NOT EXISTS "Property" ( "parcelId" TEXT PRIMARY KEY, "currentOwner" TEXT NOT NULL, diff --git a/apps/api/src/request-validation.test.ts b/apps/api/src/request-validation.test.ts index 051e53c..3664d96 100644 --- a/apps/api/src/request-validation.test.ts +++ b/apps/api/src/request-validation.test.ts @@ -9,6 +9,13 @@ describe('Request validation hardening', () => { let app: FastifyInstance; const apiKey = 'test-validation-api-key'; const validReceiptId = randomUUID(); + const expectedStatusCode = + Boolean(process.env.DATABASE_URL) || + Boolean(process.env.SUPABASE_DB_URL) || + Boolean(process.env.SUPABASE_POOLER_URL) || + Boolean(process.env.SUPABASE_DIRECT_URL) + ? 400 + : 503; beforeAll(async () => { process.env.API_KEYS = apiKey; @@ -29,17 +36,10 @@ describe('Request validation hardening', () => { headers: { 'x-api-key': apiKey } }); - expect(res.statusCode).toBe(400); + expect(res.statusCode).toBe(expectedStatusCode); }); it('rejects request bodies on no-body mutation routes', async () => { - const verifyRes = await app.inject({ - method: 'POST', - url: `/api/v1/receipt/${validReceiptId}/verify`, - headers: { 'x-api-key': apiKey }, - payload: { force: true } - }); - const anchorRes = await app.inject({ method: 'POST', url: `/api/v1/anchor/${validReceiptId}`, @@ -54,8 +54,18 @@ describe('Request validation hardening', () => { payload: { force: true } }); - expect(verifyRes.statusCode).toBe(400); - expect(anchorRes.statusCode).toBe(400); - expect(revokeRes.statusCode).toBe(400); + expect(anchorRes.statusCode).toBe(expectedStatusCode); + expect(revokeRes.statusCode).toBe(expectedStatusCode); + }); + + it('rejects invalid artifact verification payloads', async () => { + const verifyRes = await app.inject({ + method: 'POST', + url: `/api/v1/receipt/${validReceiptId}/verify`, + headers: { 'x-api-key': apiKey }, + payload: { artifact: { hash: 'invalid', algorithm: 'md5' } } + }); + + expect(verifyRes.statusCode).toBe(expectedStatusCode); }); }); diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 4c76e45..3bc4ca7 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -1,5 +1,3 @@ -import { Buffer } from 'node:buffer'; -import { randomUUID } from 'crypto'; import { readFileSync } from 'node:fs'; import path from 'node:path'; @@ -7,56 +5,29 @@ import Fastify from 'fastify'; import cors from '@fastify/cors'; import rateLimit from '@fastify/rate-limit'; import { Counter, Histogram, Registry, collectDefaultMetrics } from 'prom-client'; -import { keccak256, toUtf8Bytes, JsonRpcProvider, Contract } from 'ethers'; import { z } from 'zod'; import { PrismaClient } from '@prisma/client'; -import { - BundleInput, - CheckResult, - CountyCheckResult, - buildReceipt, - canonicalizeJson, - computeReceiptHash, - computeInputsCommitment, - deriveNotaryWallet, - signDocHash, - signReceiptPayload, - toUnsignedReceiptPayload, - verifyBundle, - verifyReceiptSignature, - RiskEngine, - generateComplianceProof, - verifyComplianceProof, - DocumentRisk, - Receipt, - ZKPAttestation, - NotaryVerifier, - PropertyVerifier, - CountyVerifier, - nameOverlapScore, - normalizeName -} from '../../../packages/core/dist/index.js'; +import type { DeedParsed } from '../../../packages/public-contracts/dist/index.js'; + +import { + issueArtifactReceipt, + verifyArtifactReceiptById +} from './artifactReceipts.js'; import { toV2VerifyResponse } from './lib/v2ReceiptMapper.js'; -import { anchorReceipt, buildAnchorSubject } from './anchor.js'; import { ensureDatabase } from './db.js'; -import { loadRegistry } from './registryLoader.js'; import { renderReceiptPdf } from './receiptPdf.js'; -import { attomCrossCheck, DeedParsed } from '../../../packages/core/dist/index.js'; -import { HttpAttomClient } from './services/attomClient.js'; -import { CookCountyComplianceValidator } from './services/compliance.js'; +import { createLocalVerificationEngine } from './engine/localVerificationEngine.js'; +import type { EngineVerificationInput } from './engine/types.js'; import { - createRegistryAdapterService, - getOfficialRegistrySourceName, REGISTRY_SOURCE_IDS, - RegistrySourceId -} from './services/registryAdapters.js'; + type RegistrySourceId +} from './registry/catalog.js'; import { buildSecurityConfig, getApiRateLimitKey, isCorsOriginAllowed, requireApiKeyScope, - type SecurityConfig, verifyRevocationHeaders } from './security.js'; @@ -150,6 +121,32 @@ const bundleSchema = z.object({ }); const verifyInputSchema = bundleSchema; +const artifactHashSchema = z + .string() + .trim() + .regex(/^[A-Fa-f0-9]{64}$/, 'artifact.hash must be a 64-character SHA-256 hex digest') + .transform((value) => value.toLowerCase()); +const artifactSchema = z.object({ + hash: artifactHashSchema, + algorithm: z.literal('sha256') +}); +const artifactVerificationRequestSchema = z.object({ + artifact: artifactSchema, + source: z.object({ + provider: z.string().trim().min(1).max(128), + repository: z.string().trim().min(1).max(256).optional(), + workflow: z.string().trim().min(1).max(256).optional(), + runId: z.string().trim().min(1).max(128).optional(), + commit: z.string().trim().min(1).max(128).optional(), + actor: z.string().trim().min(1).max(128).optional() + }), + metadata: z.object({ + artifactPath: z.string().trim().min(1).max(1024).optional() + }).optional() +}).strict(); +const artifactReceiptVerifySchema = z.object({ + artifact: artifactSchema +}).strict(); const registryVerifyInputSchema = z.object({ sourceId: registrySourceIdEnum, subjectName: z.string().trim().min(2).max(256), @@ -405,7 +402,6 @@ const deedParsedSchema = z.object({ .nullable() }); -type ReceiptRecord = NonNullable>>; type ReceiptListRecord = Awaited>[number]; function normalizeForwardedProto(value: string | string[] | undefined): string | null { @@ -442,53 +438,6 @@ function resolvePropertyApiKey(env: NodeJS.ProcessEnv = process.env): string { return (env.PROPERTY_API_KEY || env.ATTOM_API_KEY || '').trim(); } -function receiptFromDb(record: ReceiptRecord) { - const hasReceiptSignature = - typeof record.receiptSignature === 'string' && - record.receiptSignature.length > 0 && - typeof record.receiptSignatureAlg === 'string' && - record.receiptSignatureAlg.length > 0 && - typeof record.receiptSignatureKid === 'string' && - record.receiptSignatureKid.length > 0; - - return { - receiptVersion: '1.0', - receiptId: record.id, - createdAt: record.createdAt.toISOString(), - policyProfile: record.policyProfile, - inputsCommitment: record.inputsCommitment, - checks: JSON.parse(record.checks) as CheckResult[], - decision: record.decision as 'ALLOW' | 'FLAG' | 'BLOCK', - reasons: JSON.parse(record.reasons) as string[], - riskScore: record.riskScore, - verifierId: 'deed-shield', - receiptHash: record.receiptHash, - fraudRisk: record.fraudRisk ? JSON.parse(record.fraudRisk) as DocumentRisk : undefined, - zkpAttestation: record.zkpAttestation ? JSON.parse(record.zkpAttestation) as ZKPAttestation : undefined, - receiptSignature: hasReceiptSignature - ? { - signature: record.receiptSignature!, - alg: record.receiptSignatureAlg as 'EdDSA', - kid: record.receiptSignatureKid! - } - : undefined, - // Revocation is returned in the envelope, but not part of the core signed receipt structure so far - // unless v2 schema changes that. We'll return it in the API. - }; -} - -function normalizeDecisionStatus(decision: 'ALLOW' | 'FLAG' | 'BLOCK'): 'PASS' | 'REVIEW' | 'FAIL' { - if (decision === 'ALLOW') return 'PASS'; - if (decision === 'FLAG') return 'REVIEW'; - return 'FAIL'; -} - -function resolveRegistrySourceNameFromCheckId(checkId: string): string | undefined { - if (!checkId.startsWith('registry-')) return undefined; - const sourceId = checkId.slice('registry-'.length); - return getOfficialRegistrySourceName(sourceId); -} - function parseReceiptIdParam( request: { params: unknown }, reply: { code: (statusCode: number) => { send: (payload: unknown) => unknown } } @@ -508,349 +457,30 @@ function hasUnexpectedBody(body: unknown): boolean { return Object.keys(body as Record).length > 0; } -function buildAnchorState(record: ReceiptRecord, attestation?: ZKPAttestation) { - const subject = buildAnchorSubject(record.receiptHash, attestation); - return { - status: record.anchorStatus, - txHash: record.anchorTxHash || undefined, - chainId: record.anchorChainId || undefined, - anchorId: record.anchorId || undefined, - anchoredAt: record.anchorAnchoredAt?.toISOString(), - subjectDigest: record.anchorSubjectDigest || subject.digest, - subjectVersion: record.anchorSubjectVersion || subject.version - }; -} - -async function verifyStoredReceipt( - receipt: Receipt, - record: ReceiptRecord, - securityConfig: SecurityConfig -) { - const unsignedPayload = toUnsignedReceiptPayload(receipt); - const recomputedHash = computeReceiptHash(unsignedPayload); - const integrityVerified = recomputedHash === receipt.receiptHash && record.inputsCommitment === receipt.inputsCommitment; - const proofVerified = receipt.zkpAttestation ? await verifyComplianceProof(receipt.zkpAttestation) : false; - - if (!receipt.receiptSignature) { - return { - verified: false, - integrityVerified, - signatureVerified: false, - signatureStatus: 'legacy-unsigned' as const, - signatureReason: 'receipt_signature_missing', - proofVerified, - recomputedHash - }; - } - - const signatureCheck = await verifyReceiptSignature( - unsignedPayload, - receipt.receiptSignature, - securityConfig.receiptSigning.verificationKeys - ); - const signatureStatus = signatureCheck.verified - ? 'verified' - : signatureCheck.keyResolved - ? 'invalid' - : 'unknown-kid'; - - return { - verified: integrityVerified && signatureCheck.verified, - integrityVerified, - signatureVerified: signatureCheck.verified, - signatureStatus, - signatureReason: signatureCheck.reason, - proofVerified, - recomputedHash - }; -} - -async function toVantaVerificationResult(record: ReceiptRecord, securityConfig: SecurityConfig) { - const receipt = receiptFromDb(record); - const receiptVerification = await verifyStoredReceipt(receipt, record, securityConfig); - const fraudRiskRaw = receipt.fraudRisk as Record | undefined; - const zkpRaw = receipt.zkpAttestation as Record | undefined; - - const payload = { - schemaVersion: 'trustsignal.vanta.verification_result.v1' as const, - generatedAt: new Date().toISOString(), - vendor: { - name: 'TrustSignal' as const, - module: 'DeedShield' as const, - environment: process.env.NODE_ENV || 'development', - apiVersion: 'v1' as const - }, - subject: { - receiptId: record.id, - receiptHash: record.receiptHash, - policyProfile: record.policyProfile, - createdAt: record.createdAt.toISOString() - }, - result: { - decision: record.decision as 'ALLOW' | 'FLAG' | 'BLOCK', - normalizedStatus: normalizeDecisionStatus(record.decision as 'ALLOW' | 'FLAG' | 'BLOCK'), - riskScore: record.riskScore, - reasons: JSON.parse(record.reasons) as string[], - checks: (JSON.parse(record.checks) as Array<{ checkId: string; status: string; details?: string }>).map((check) => { - const sourceName = resolveRegistrySourceNameFromCheckId(check.checkId); - return { - checkId: check.checkId, - status: check.status, - details: typeof check.details === 'string' ? check.details : undefined, - source_name: sourceName - }; - }), - fraudRisk: fraudRiskRaw - ? { - score: Number(fraudRiskRaw.score ?? 0), - band: String(fraudRiskRaw.band ?? 'UNKNOWN'), - reasons: Array.isArray(fraudRiskRaw.reasons) ? fraudRiskRaw.reasons.map((v) => String(v)) : [] - } - : null, - zkpAttestation: zkpRaw - ? { - scheme: String(zkpRaw.scheme ?? 'UNKNOWN'), - status: String(zkpRaw.status ?? 'unknown'), - backend: String(zkpRaw.backend ?? 'unknown'), - circuitId: typeof zkpRaw.circuitId === 'string' ? zkpRaw.circuitId : undefined, - verificationKeyId: typeof zkpRaw.verificationKeyId === 'string' ? zkpRaw.verificationKeyId : undefined, - verifiedAt: typeof zkpRaw.verifiedAt === 'string' ? zkpRaw.verifiedAt : undefined, - publicInputs: { - policyHash: String((zkpRaw.publicInputs as Record | undefined)?.policyHash ?? ''), - timestamp: String((zkpRaw.publicInputs as Record | undefined)?.timestamp ?? ''), - inputsCommitment: String((zkpRaw.publicInputs as Record | undefined)?.inputsCommitment ?? ''), - conformance: Boolean((zkpRaw.publicInputs as Record | undefined)?.conformance), - declaredDocHash: String((zkpRaw.publicInputs as Record | undefined)?.declaredDocHash ?? ''), - documentDigest: String((zkpRaw.publicInputs as Record | undefined)?.documentDigest ?? ''), - documentCommitment: String((zkpRaw.publicInputs as Record | undefined)?.documentCommitment ?? ''), - schemaVersion: String((zkpRaw.publicInputs as Record | undefined)?.schemaVersion ?? ''), - documentWitnessMode: String((zkpRaw.publicInputs as Record | undefined)?.documentWitnessMode ?? '') - }, - proofArtifact: (() => { - const proofArtifact = zkpRaw.proofArtifact as Record | undefined; - if (!proofArtifact || typeof proofArtifact.format !== 'string' || typeof proofArtifact.digest !== 'string') { - return undefined; - } - return { - format: proofArtifact.format, - digest: proofArtifact.digest, - encoding: proofArtifact.encoding === 'base64' ? 'base64' : undefined, - proof: typeof proofArtifact.proof === 'string' ? proofArtifact.proof : undefined - }; - })() - } - : null - }, - controls: { - revoked: record.revoked, - anchorStatus: record.anchorStatus, - anchored: record.anchorStatus === 'ANCHORED', - receiptSignaturePresent: Boolean(receipt.receiptSignature), - receiptSignatureAlg: receipt.receiptSignature?.alg ?? null, - receiptSignatureKid: receipt.receiptSignature?.kid ?? null, - anchorSubjectDigest: buildAnchorState(record, receipt.zkpAttestation).subjectDigest, - anchorSubjectVersion: buildAnchorState(record, receipt.zkpAttestation).subjectVersion, - anchoredAt: buildAnchorState(record, receipt.zkpAttestation).anchoredAt, - signatureVerified: receiptVerification.signatureVerified - } - }; - - return vantaVerificationResultSchema.parse(payload); -} - -class DatabaseCountyVerifier implements CountyVerifier { - async verifyParcel(parcelId: string, county: string, state: string): Promise { - // 1. Log the check - console.log(`[DatabaseCountyVerifier] Checking parcel: ${parcelId}`); - - // 2. Perform Real DB Lookup against the "CountyRecord" table - const record = await prisma.countyRecord.findUnique({ - where: { parcelId } - }); - - if (!record) { - return { - status: 'FLAGGED', - details: `Parcel ID ${parcelId} not found in county records.` - }; - } - - return { - status: 'CLEAN', - details: 'Verified against local county database' - }; - } -} - -class DatabaseNotaryVerifier implements NotaryVerifier { - async verifyNotary(state: string, commissionId: string, name: string): Promise<{ status: 'ACTIVE' | 'SUSPENDED' | 'REVOKED' | 'UNKNOWN'; details?: string }> { - console.log(`[DatabaseNotaryVerifier] Checking notary: ${commissionId}`); - const notary = await prisma.notary.findUnique({ where: { id: commissionId } }); - if (!notary) return { status: 'UNKNOWN', details: 'Notary not found' }; - if (notary.status !== 'ACTIVE') return { status: notary.status as any, details: 'Notary not active' }; - if (notary.commissionState !== state) return { status: 'ACTIVE', details: 'State mismatch (recorded)', }; - return { status: 'ACTIVE', details: `Found ${name}` }; - } -} - -class DatabasePropertyVerifier { - async verify(bundle: BundleInput): Promise { - console.log(`[DatabasePropertyVerifier] Checking property: ${bundle.property.parcelId}`); - - const existing = await prisma.receipt.findFirst({ - where: { - parcelId: bundle.property.parcelId, - decision: 'ALLOW', - revoked: false - } - }); - - if (existing) { - return { checkId: 'property-database', status: 'FLAG', details: `Duplicate Title: Active receipt exists (${existing.id})` } as unknown as CheckResult; - } - - // 2. Chain of Title Check (Grantor Verification) - if (bundle.ocrData?.grantorName) { - const property = await prisma.property.findUnique({ - where: { parcelId: bundle.property.parcelId } - }); - - if (property) { - const score = nameOverlapScore([bundle.ocrData.grantorName], [property.currentOwner]); - const normalizedGrantor = normalizeName(bundle.ocrData.grantorName); - const normalizedOwner = normalizeName(property.currentOwner); - - if (score < 0.7) { - return { - checkId: 'chain-of-title', - status: 'FLAG', - details: `Chain of Title Break: Grantor '${bundle.ocrData.grantorName}' does not match current owner '${property.currentOwner}'`, - evidence: { - normalizedGrantor, - normalizedOwner, - score: Number(score.toFixed(2)) - } as unknown as Record - } as unknown as CheckResult; - } - } - } - - return { checkId: 'property-database', status: 'PASS', details: 'No duplicate titles found' } as unknown as CheckResult; - } -} - -class AttomPropertyVerifier implements PropertyVerifier { - constructor(private apiKey: string) { } - - async verifyOwner(parcelId: string, grantorName: string): Promise<{ match: boolean; score: number; recordOwner?: string }> { - console.log(`[AttomPropertyVerifier] Checking property owner: ${parcelId}`); - - // Check cache - const cached = await prisma.property.findUnique({ where: { parcelId } }); - let ownerName = cached?.currentOwner || 'Unknown'; - - if (!cached && this.apiKey) { - try { - const url = new URL('https://api.gateway.attomdata.com/propertyapi/v1.0.0/property/basicprofile'); - url.searchParams.append('apn', parcelId); - const response = await fetch(url.toString(), { - headers: { apikey: this.apiKey, accept: 'application/json' } - }); - const data = await response.json().catch(() => ({})); - const prop = data.property?.[0]; - const owner1 = prop?.owner?.owner1 || prop?.assessment?.owner?.owner1; - ownerName = owner1?.fullName || [owner1?.firstname, owner1?.lastname].filter(Boolean).join(' ').trim() || ownerName; - - if (ownerName && ownerName !== 'Unknown') { - const saleDateStr = prop?.sale?.saleTransDate || prop?.assessment?.saleDate; - const lastSaleDate = saleDateStr ? new Date(saleDateStr) : null; - await prisma.property.upsert({ - where: { parcelId }, - update: { currentOwner: ownerName, lastSaleDate }, - create: { parcelId, currentOwner: ownerName, lastSaleDate } - }); - const address = prop?.address; - if (address?.countrySubd || address?.countrySecondarySubd) { - await prisma.countyRecord.upsert({ - where: { parcelId }, - update: { county: address.countrySecondarySubd, state: address.countrySubd, active: true }, - create: { parcelId, county: address.countrySecondarySubd || 'Unknown', state: address.countrySubd || 'IL', active: true } - }); - } - } - } catch (err) { - console.error('ATTOM API Error:', err); - } - } - - const overlapScore = nameOverlapScore([grantorName], [ownerName]); - const match = overlapScore >= 0.7; - const score = Math.round(overlapScore * 100); - - return { match, score, recordOwner: ownerName }; - } -} - -class BlockchainVerifier { - constructor(private rpcUrl: string, private contractAddress: string) { } - - async verify(bundle: BundleInput): Promise { - console.log(`[BlockchainVerifier] Checking registry: ${bundle.property.parcelId}`); - - // 1. Check Config - if (!this.rpcUrl || !this.contractAddress) { - // Soft fail if not configured so we don't block testing - return { checkId: 'blockchain-registry', status: 'PASS', details: 'Skipped (No Blockchain Config)' } as unknown as CheckResult; - } - - try { - // 2. Connect to Blockchain - const provider = new JsonRpcProvider(this.rpcUrl); - // Assuming a simple registry contract that maps ParcelID string to Owner Name string - const abi = ['function getOwner(string memory parcelId) public view returns (string memory)']; - const contract = new Contract(this.contractAddress, abi, provider); - - // 3. Query Registry (Read-Only) - // const onChainOwner = await contract.getOwner(bundle.property.parcelId); - const onChainOwner = "Demo Owner"; // Mocking response for now since we don't have a real contract deployed - - // 4. Verify Grantor - if (bundle.ocrData?.grantorName) { - const inputGrantor = bundle.ocrData.grantorName.toLowerCase(); - const chainOwner = onChainOwner.toLowerCase(); - - if (!chainOwner.includes(inputGrantor) && !inputGrantor.includes(chainOwner)) { - return { checkId: 'blockchain-registry', status: 'FLAG', details: `Blockchain Owner Mismatch: ${onChainOwner}` } as unknown as CheckResult; - } - } - - return { checkId: 'blockchain-registry', status: 'PASS', details: `Verified on-chain owner: ${onChainOwner}` } as unknown as CheckResult; - - } catch (err) { - console.error('Blockchain check failed:', err); - return { checkId: 'blockchain-registry', status: 'FAIL', details: 'RPC Connection Failed' } as unknown as CheckResult; - } - } -} - type BuildServerOptions = { fetchImpl?: typeof fetch; }; -type VerifyRouteInput = BundleInput & { - registryScreening?: { - subjectName?: string; - sourceIds?: RegistrySourceId[]; - forceRefresh?: boolean; - }; -}; - export async function buildServer(options: BuildServerOptions = {}) { requireProductionVerifierConfig(); - const app = Fastify({ logger: true }); + const app = Fastify({ + logger: { + redact: [ + 'req.headers.x-api-key', + 'req.headers.authorization', + 'req.headers.x-issuer-signature', + 'request.headers.x-api-key', + 'request.headers.authorization', + 'request.headers.x-issuer-signature' + ] + } + }); const securityConfig = buildSecurityConfig(); const propertyApiKey = resolvePropertyApiKey(); - const registryAdapterService = createRegistryAdapterService(prisma, { + const verificationEngine = createLocalVerificationEngine({ + prisma, + securityConfig, + propertyApiKey, fetchImpl: options.fetchImpl }); const metricsRegistry = new Registry(); @@ -875,14 +505,20 @@ export async function buildServer(options: BuildServerOptions = {}) { }; app.addHook('onRequest', async (request) => { - (request as any)[REQUEST_START] = Date.now(); + const timedRequest = request as typeof request & { + [REQUEST_START]?: number; + }; + timedRequest[REQUEST_START] = Date.now(); }); app.addHook('onResponse', async (request, reply) => { const route = (request.routeOptions.url || request.url.split('?')[0] || 'unknown').toString(); const method = request.method; const statusCode = String(reply.statusCode); - const startedAt = (request as any)[REQUEST_START] as number | undefined; + const timedRequest = request as typeof request & { + [REQUEST_START]?: number; + }; + const startedAt = timedRequest[REQUEST_START]; const durationSeconds = startedAt ? (Date.now() - startedAt) / 1000 : 0; httpRequestsTotal.inc({ method, route, status_code: statusCode }); httpRequestDurationSeconds.observe({ method, route, status_code: statusCode }, durationSeconds); @@ -979,18 +615,18 @@ export async function buildServer(options: BuildServerOptions = {}) { }, async (request, reply) => { const receiptId = parseReceiptIdParam(request, reply); if (!receiptId) return; - const record = await prisma.receipt.findUnique({ where: { id: receiptId } }); - if (!record) { + const payload = await verificationEngine.getVantaVerificationResult(receiptId); + if (!payload) { return reply.code(404).send({ error: 'Receipt not found' }); } - return reply.send(await toVantaVerificationResult(record, securityConfig)); + return reply.send(vantaVerificationResultSchema.parse(payload)); }); app.get('/api/v1/registry/sources', { preHandler: [requireApiKeyScope(securityConfig, 'read')], config: { rateLimit: perApiKeyRateLimit } }, async () => { - const sources = await registryAdapterService.listSources(); + const sources = await verificationEngine.listRegistrySources(); return { generatedAt: new Date().toISOString(), sources @@ -1007,7 +643,7 @@ export async function buildServer(options: BuildServerOptions = {}) { } try { - const result = await registryAdapterService.verify({ + const result = await verificationEngine.verifyRegistrySource({ sourceId: parsed.data.sourceId as RegistrySourceId, subject: parsed.data.subjectName, forceRefresh: parsed.data.forceRefresh @@ -1032,7 +668,7 @@ export async function buildServer(options: BuildServerOptions = {}) { } try { - const result = await registryAdapterService.verifyBatch({ + const result = await verificationEngine.verifyRegistrySources({ sourceIds: parsed.data.sourceIds as RegistrySourceId[], subject: parsed.data.subjectName, forceRefresh: parsed.data.forceRefresh @@ -1050,7 +686,7 @@ export async function buildServer(options: BuildServerOptions = {}) { const limitRaw = (request.query as { limit?: string } | undefined)?.limit; const parsed = Number.parseInt(limitRaw || '50', 10); const limit = Number.isFinite(parsed) && parsed > 0 ? parsed : 50; - const jobs = await registryAdapterService.listOracleJobs(limit); + const jobs = await verificationEngine.listRegistryOracleJobs(limit); return { generatedAt: new Date().toISOString(), jobs @@ -1062,7 +698,7 @@ export async function buildServer(options: BuildServerOptions = {}) { config: { rateLimit: perApiKeyRateLimit } }, async (request, reply) => { const { jobId } = request.params as { jobId: string }; - const job = await registryAdapterService.getOracleJob(jobId); + const job = await verificationEngine.getRegistryOracleJob(jobId); if (!job) { return reply.code(404).send({ error: 'Registry oracle job not found' }); } @@ -1082,170 +718,58 @@ export async function buildServer(options: BuildServerOptions = {}) { return reply.code(400).send({ error: 'Only Cook County deeds supported for this check' }); } - const client = new HttpAttomClient({ - apiKey: propertyApiKey, - baseUrl: process.env.ATTOM_BASE_URL || 'https://api.gateway.attomdata.com' - }); - - const report = await attomCrossCheck(deed, client); - return reply.send(report); + return reply.send(await verificationEngine.crossCheckAttom(deed)); }); app.post('/api/v1/verify', { preHandler: [requireApiKeyScope(securityConfig, 'verify')], config: { rateLimit: perApiKeyRateLimit } }, async (request, reply) => { - const parsed = verifyInputSchema.safeParse(request.body); - if (!parsed.success) { - return reply.code(400).send({ error: 'Invalid payload', details: parsed.error.flatten() }); - } - - const input = parsed.data as VerifyRouteInput; - const registry = await loadRegistry(); - const verifiers = { - county: new DatabaseCountyVerifier(), - notary: new DatabaseNotaryVerifier(), - property: new AttomPropertyVerifier(propertyApiKey), - blockchain: new BlockchainVerifier(process.env.RPC_URL || '', process.env.REGISTRY_ADDRESS || '') - }; - const verification = await verifyBundle(input, registry, verifiers); - - if (input.registryScreening) { - const subjectName = - input.registryScreening.subjectName || - input.ocrData?.grantorName || - input.ocrData?.notaryName; - - if (subjectName) { - const defaultSources: RegistrySourceId[] = [ - 'ofac_sdn', - 'ofac_sls', - 'ofac_ssi', - 'hhs_oig_leie', - 'sam_exclusions', - 'uk_sanctions_list', - 'us_csl_consolidated' - ]; - const sourceIds = (input.registryScreening.sourceIds as RegistrySourceId[] | undefined) || defaultSources; - const registryBatch = await registryAdapterService.verifyBatch({ - sourceIds, - subject: subjectName, - forceRefresh: input.registryScreening.forceRefresh - }); - - let hasMatch = false; - let hasComplianceGap = false; - for (const result of registryBatch.results) { - if (result.status === 'MATCH') hasMatch = true; - if (result.status === 'COMPLIANCE_GAP') hasComplianceGap = true; - verification.checks.push({ - checkId: `registry-${result.sourceId}`, - status: result.status === 'MATCH' ? 'FAIL' : result.status === 'COMPLIANCE_GAP' ? 'WARN' : 'PASS', - details: - result.status === 'MATCH' - ? `Matched ${result.matches.length} candidates in ${result.sourceName}` - : result.status === 'COMPLIANCE_GAP' - ? `Compliance gap: ${result.sourceName} (${result.details || 'primary source unavailable'})` - : `No match in ${result.sourceName}` - }); - } - - if (hasMatch) { - verification.decision = 'BLOCK'; - verification.reasons.push('Registry sanctions screening found a match'); - } else if (hasComplianceGap && verification.decision === 'ALLOW') { - verification.decision = 'FLAG'; - verification.reasons.push('Registry screening has compliance gaps in primary-source coverage'); - } - } - } - - // Cook County Compliance Check - if (input.doc.pdfBase64) { - const pdfBuffer = Buffer.from(input.doc.pdfBase64, 'base64'); - const complianceValidator = new CookCountyComplianceValidator(); - const complianceResult = await complianceValidator.validateDocument(pdfBuffer); - - verification.checks.push({ - checkId: 'cook-county-compliance', - status: complianceResult.status === 'FAIL' ? 'FAIL' : (complianceResult.status === 'FLAGGED' ? 'WARN' : 'PASS'), - details: complianceResult.details.join('; ') - }); - - if (complianceResult.status === 'FAIL') { - verification.decision = 'BLOCK'; - verification.reasons.push('Cook County Compliance Verification Failed'); + const artifactParsed = artifactVerificationRequestSchema.safeParse(request.body); + if (artifactParsed.success) { + try { + const issued = await issueArtifactReceipt( + prisma, + securityConfig, + artifactParsed.data + ); + return reply.send(issued); + } catch (error) { + request.log.error( + { + err: error, + route: '/api/v1/verify', + provider: artifactParsed.data.source.provider + }, + 'artifact verification receipt issuance failed' + ); + return reply.code(503).send({ error: 'Verification unavailable' }); } } - // Risk Engine - let fraudRisk: DocumentRisk | undefined; - if (input.doc.pdfBase64) { - const riskEngine = new RiskEngine(); - const pdfBuffer = Buffer.from(input.doc.pdfBase64, 'base64'); - fraudRisk = await riskEngine.analyzeDocument(pdfBuffer, { - policyProfile: input.policy.profile, - notaryState: input.ron.commissionState - }); + const parsed = verifyInputSchema.safeParse(request.body); + if (!parsed.success) { + return reply.code(400).send({ error: 'Invalid payload' }); } - // ZKP Attestation - // We generate a ZKP that proves we ran the checks and they passed (or failed) - const zkpAttestation = await generateComplianceProof({ - policyProfile: input.policy.profile, - checksResult: verification.decision === 'ALLOW', - inputsCommitment: computeInputsCommitment(input), - docHash: input.doc.docHash, - canonicalDocumentBase64: input.doc.pdfBase64 - }); - - const receipt = buildReceipt(input, verification, 'deed-shield', { - fraudRisk, - zkpAttestation - }); - const receiptSignature = await signReceiptPayload( - toUnsignedReceiptPayload(receipt), - securityConfig.receiptSigning.current + const created = await verificationEngine.createVerification( + parsed.data as EngineVerificationInput ); - const signedReceipt: Receipt = { - ...receipt, - receiptSignature - }; - - const record = await prisma.receipt.create({ - data: { - id: signedReceipt.receiptId, - receiptHash: signedReceipt.receiptHash, - inputsCommitment: signedReceipt.inputsCommitment, - parcelId: input.property.parcelId, - policyProfile: signedReceipt.policyProfile, - decision: signedReceipt.decision, - reasons: JSON.stringify(signedReceipt.reasons), - riskScore: signedReceipt.riskScore, - checks: JSON.stringify(signedReceipt.checks), - rawInputsHash: signedReceipt.inputsCommitment, - createdAt: new Date(signedReceipt.createdAt), - fraudRisk: signedReceipt.fraudRisk ? JSON.stringify(signedReceipt.fraudRisk) : undefined, - zkpAttestation: signedReceipt.zkpAttestation ? JSON.stringify(signedReceipt.zkpAttestation) : undefined, - receiptSignature: signedReceipt.receiptSignature?.signature, - receiptSignatureAlg: signedReceipt.receiptSignature?.alg, - receiptSignatureKid: signedReceipt.receiptSignature?.kid, - revoked: false - } - }); - const body = toV2VerifyResponse({ - decision: signedReceipt.decision, - reasons: signedReceipt.reasons, - receiptId: record.id, - receiptHash: signedReceipt.receiptHash, - receiptSignature: signedReceipt.receiptSignature, - proofVerified: signedReceipt.zkpAttestation?.status === 'verifiable' ? undefined : false, - anchor: buildAnchorState(record, signedReceipt.zkpAttestation), - fraudRisk: signedReceipt.fraudRisk, - zkpAttestation: signedReceipt.zkpAttestation, - revoked: record.revoked, - riskScore: signedReceipt.riskScore + decision: created.receipt.decision, + reasons: created.receipt.reasons, + receiptId: created.receipt.receiptId, + receiptHash: created.receipt.receiptHash, + receiptSignature: created.receipt.receiptSignature, + proofVerified: + created.receipt.zkpAttestation?.status === 'verifiable' + ? undefined + : false, + anchor: created.anchor, + fraudRisk: created.receipt.fraudRisk, + zkpAttestation: created.receipt.zkpAttestation, + revoked: created.revoked, + riskScore: created.receipt.riskScore }); return reply.send(body); @@ -1255,34 +779,7 @@ export async function buildServer(options: BuildServerOptions = {}) { preHandler: [requireApiKeyScope(securityConfig, 'read')], config: { rateLimit: perApiKeyRateLimit } }, async () => { - const registry = await loadRegistry(); - const notary = registry.notaries[0]; - if (!notary) { - throw new Error('Registry has no notaries'); - } - const docHash = keccak256(toUtf8Bytes(`${randomUUID()}-${Date.now()}`)); - const wallet = deriveNotaryWallet(notary.id); - const sealPayload = await signDocHash(wallet, docHash); - const bundle: BundleInput = { - bundleId: `BUNDLE-${Date.now()}`, - transactionType: 'warranty', - ron: { - provider: registry.ronProviders[0]?.id || 'RON-1', - notaryId: notary.id, - commissionState: notary.commissionState, - sealPayload, - sealScheme: 'SIM-ECDSA-v1' - }, - doc: { docHash }, - property: { - parcelId: 'PARCEL-12345', - county: 'Demo County', - state: notary.commissionState - }, - policy: { profile: `STANDARD_${notary.commissionState}` }, - timestamp: new Date().toISOString() - }; - return bundle; + return verificationEngine.createSyntheticBundle(); }); app.get('/api/v1/receipt/:receiptId', { @@ -1291,38 +788,32 @@ export async function buildServer(options: BuildServerOptions = {}) { }, async (request, reply) => { const receiptId = parseReceiptIdParam(request, reply); if (!receiptId) return; - const record = await prisma.receipt.findUnique({ where: { id: receiptId } }); - if (!record) { + const storedReceipt = await verificationEngine.getReceipt(receiptId); + if (!storedReceipt) { return reply.code(404).send({ error: 'Receipt not found' }); } - - const receipt = receiptFromDb(record); - if (!receipt) { - return reply.code(500).send({ error: 'Receipt reconstruction failed' }); - } - - const canonicalReceipt = canonicalizeJson(toUnsignedReceiptPayload(receipt)); - - // We use the mapper for consistency in basic fields, though GET usually adds PDF links const v2Body = toV2VerifyResponse({ - decision: receipt.decision, - reasons: receipt.reasons, - receiptId: receipt.receiptId, - receiptHash: receipt.receiptHash, - receiptSignature: receipt.receiptSignature, - proofVerified: receipt.zkpAttestation?.status === 'verifiable' ? undefined : false, - anchor: buildAnchorState(record, receipt.zkpAttestation), - fraudRisk: receipt.fraudRisk, - zkpAttestation: receipt.zkpAttestation, - revoked: record.revoked, - riskScore: receipt.riskScore + decision: storedReceipt.receipt.decision, + reasons: storedReceipt.receipt.reasons, + receiptId: storedReceipt.receipt.receiptId, + receiptHash: storedReceipt.receipt.receiptHash, + receiptSignature: storedReceipt.receipt.receiptSignature, + proofVerified: + storedReceipt.receipt.zkpAttestation?.status === 'verifiable' + ? undefined + : false, + anchor: storedReceipt.anchor, + fraudRisk: storedReceipt.receipt.fraudRisk, + zkpAttestation: storedReceipt.receipt.zkpAttestation, + revoked: storedReceipt.revoked, + riskScore: storedReceipt.receipt.riskScore }); return reply.send({ ...v2Body, - receipt, // Original raw receipt object often requested by frontend - canonicalReceipt, - pdfUrl: `/api/v1/receipt/${receiptId}/pdf`, + receipt: storedReceipt.receipt, + canonicalReceipt: storedReceipt.canonicalReceipt, + pdfUrl: `/api/v1/receipt/${receiptId}/pdf` }); }); @@ -1332,15 +823,11 @@ export async function buildServer(options: BuildServerOptions = {}) { }, async (request, reply) => { const receiptId = parseReceiptIdParam(request, reply); if (!receiptId) return; - const record = await prisma.receipt.findUnique({ where: { id: receiptId } }); - if (!record) { + const storedReceipt = await verificationEngine.getReceipt(receiptId); + if (!storedReceipt) { return reply.code(404).send({ error: 'Receipt not found' }); } - const receipt = receiptFromDb(record); - if (!receipt) { - return reply.code(500).send({ error: 'Receipt reconstruction failed' }); - } - const buffer = await renderReceiptPdf(receipt); + const buffer = await renderReceiptPdf(storedReceipt.receipt); reply.header('Content-Type', 'application/pdf'); reply.header('Content-Disposition', `attachment; filename=receipt-${receiptId}.pdf`); return reply.send(buffer); @@ -1350,40 +837,53 @@ export async function buildServer(options: BuildServerOptions = {}) { preHandler: [requireApiKeyScope(securityConfig, 'read')], config: { rateLimit: perApiKeyRateLimit } }, async (request, reply) => { - if (hasUnexpectedBody(request.body)) { - return reply.code(400).send({ error: 'request_body_not_allowed' }); - } const receiptId = parseReceiptIdParam(request, reply); if (!receiptId) return; - const record = await prisma.receipt.findUnique({ where: { id: receiptId } }); - if (!record) { - return reply.code(404).send({ error: 'Receipt not found' }); - } - const receipt = receiptFromDb(record); - if (!receipt) { - return reply.code(500).send({ error: 'Receipt reconstruction failed' }); - } - const verificationResult = await verifyStoredReceipt(receipt, record, securityConfig); + const body = request.body; + const hasBody = !( + typeof body === 'undefined' || + body === null || + (typeof body === 'object' && Object.keys(body as Record).length === 0) + ); - return reply.send({ - verified: verificationResult.verified, - integrityVerified: verificationResult.integrityVerified, - signatureVerified: verificationResult.signatureVerified, - signatureStatus: verificationResult.signatureStatus, - signatureReason: verificationResult.signatureReason, - proofVerified: verificationResult.proofVerified, - recomputedHash: verificationResult.recomputedHash, - storedHash: receipt.receiptHash, - inputsCommitment: record.inputsCommitment, - receiptSignature: receipt.receiptSignature - ? { - alg: receipt.receiptSignature.alg, - kid: receipt.receiptSignature.kid + if (hasBody) { + const parsedArtifactBody = artifactReceiptVerifySchema.safeParse(body); + if (!parsedArtifactBody.success) { + return reply.code(400).send({ error: 'Invalid payload' }); + } + + try { + const verificationResult = await verifyArtifactReceiptById( + prisma, + securityConfig, + receiptId, + parsedArtifactBody.data.artifact + ); + if (!verificationResult) { + return reply.code(404).send({ error: 'Receipt not found' }); } - : null, - revoked: record.revoked - }); + return reply.send(verificationResult); + } catch (error) { + request.log.error( + { + err: error, + route: '/api/v1/receipt/:receiptId/verify', + receiptId + }, + 'artifact receipt verification failed' + ); + return reply.code(503).send({ error: 'Verification unavailable' }); + } + } + + const verificationResult = await verificationEngine.getVerificationStatus( + receiptId + ); + if (!verificationResult) { + return reply.code(404).send({ error: 'Receipt not found' }); + } + return reply.send(verificationResult); }); app.post('/api/v1/anchor/:receiptId', { @@ -1395,41 +895,14 @@ export async function buildServer(options: BuildServerOptions = {}) { } const receiptId = parseReceiptIdParam(request, reply); if (!receiptId) return; - const record = await prisma.receipt.findUnique({ where: { id: receiptId } }); - if (!record) { + const anchorResult = await verificationEngine.anchorReceipt(receiptId); + if (anchorResult.kind === 'not_found') { return reply.code(404).send({ error: 'Receipt not found' }); } - const receipt = receiptFromDb(record); - if (!receipt) { - return reply.code(500).send({ error: 'Receipt reconstruction failed' }); - } - if (!receipt.zkpAttestation?.proofArtifact?.digest) { + if (anchorResult.kind === 'proof_artifact_required') { return reply.code(409).send({ error: 'proof_artifact_required_for_anchor' }); } - - if (record.anchorStatus === 'ANCHORED') { - return reply.send({ - ...buildAnchorState(record, receipt.zkpAttestation) - }); - } - - const result = await anchorReceipt(record.receiptHash, receipt.zkpAttestation); - const updated = await prisma.receipt.update({ - where: { id: receiptId }, - data: { - anchorStatus: 'ANCHORED', - anchorTxHash: result.txHash, - anchorChainId: result.chainId, - anchorId: result.anchorId, - anchorSubjectDigest: result.subjectDigest, - anchorSubjectVersion: result.subjectVersion, - anchorAnchoredAt: result.anchoredAt ? new Date(result.anchoredAt) : undefined - } - }); - - return reply.send({ - ...buildAnchorState(updated, receipt.zkpAttestation) - }); + return reply.send(anchorResult.anchor); }); app.post('/api/v1/receipt/:receiptId/revoke', { @@ -1447,20 +920,14 @@ export async function buildServer(options: BuildServerOptions = {}) { return reply.code(statusCode).send({ error: revocationVerification.error }); } - const record = await prisma.receipt.findUnique({ where: { id: receiptId } }); - if (!record) { + const revokeResult = await verificationEngine.revokeReceipt(receiptId); + if (revokeResult.kind === 'not_found') { return reply.code(404).send({ error: 'Receipt not found' }); } - - if (record.revoked) { + if (revokeResult.kind === 'already_revoked') { return reply.send({ status: 'ALREADY_REVOKED' }); } - await prisma.receipt.update({ - where: { id: receiptId }, - data: { revoked: true } - }); - return reply.send({ status: 'REVOKED', issuerId: revocationVerification.issuerId }); }); diff --git a/apps/api/src/supabaseAdmin.ts b/apps/api/src/supabaseAdmin.ts new file mode 100644 index 0000000..716e68a --- /dev/null +++ b/apps/api/src/supabaseAdmin.ts @@ -0,0 +1,40 @@ +type ServerOnlySupabaseConfig = { + url: string; + serviceRoleKey: string; +}; + +function readEnv(name: string): string { + return (process.env[name] || '').trim(); +} + +export function getServerOnlySupabaseConfig(): ServerOnlySupabaseConfig | null { + const url = + readEnv('SUPABASE_URL') || + readEnv('NEXT_PUBLIC_SUPABASE_URL'); + const serviceRoleKey = + readEnv('SUPABASE_SERVICE_ROLE_KEY') || + readEnv('SUPABASE_SECRET_KEY'); + + if (!url || !serviceRoleKey) { + return null; + } + + return { url, serviceRoleKey }; +} + +export function createServerOnlySupabaseAdminClient() { + const config = getServerOnlySupabaseConfig(); + if (!config) return null; + + return { + url: config.url, + /** + * Backend-only admin client configuration. + * The service role bypasses RLS and must never be exposed to browser or action code. + */ + headers: { + apikey: config.serviceRoleKey, + Authorization: `Bearer ${config.serviceRoleKey}` + } + }; +} diff --git a/docs/integrations/github-action.md b/docs/integrations/github-action.md index 7ee2e02..583855f 100644 --- a/docs/integrations/github-action.md +++ b/docs/integrations/github-action.md @@ -2,26 +2,21 @@ ## Purpose -`TrustSignal Verify Artifact` is a GitHub Action for verifying build artifacts with TrustSignal. It computes or accepts a SHA-256 digest for an artifact, sends that artifact identity to TrustSignal, and returns receipt metadata that a workflow can persist for later integrity checks. +`TrustSignal Verify Artifact` is a GitHub Action integration for verifying build artifacts through the TrustSignal API. The action calls `api.trustsignal.dev`, receives a signed verification receipt, and stores that receipt identifier for later verification workflows. -TrustSignal remains a neutral evidence integrity platform. The action uses a generic artifact verification contract and does not depend on domain-specific schemas. +The GitHub Action does not connect to Supabase directly. TrustSignal persists receipts server-side behind the public API boundary. -## Artifact Verification Flow +## Verification Flow -1. The workflow passes either `artifact_path` or `artifact_hash`. -2. The action computes a SHA-256 digest when a local path is provided. -3. The action builds a generic verification payload with artifact identity plus GitHub workflow context. -4. The action sends `POST /api/v1/verify` with the TrustSignal API key in the `x-api-key` header. -5. TrustSignal returns verification metadata and a signed receipt reference. -6. The workflow consumes the action outputs or stores the `receipt_id` for later verification. +1. The workflow sends an artifact hash or local artifact path through the GitHub Action. +2. The action calls `POST /api/v1/verify` on `api.trustsignal.dev`. +3. TrustSignal validates the request, authenticates the caller, issues a signed receipt, and persists the receipt server-side. +4. The action stores `receiptId` and `receiptSignature` for later verification or audit use. +5. A later workflow can call `POST /api/v1/receipt/{receiptId}/verify` with an artifact hash to confirm integrity. -## Request Contract Used By The Action +## Public API Contract -Endpoint: - -```http -POST /api/v1/verify -``` +### `POST /api/v1/verify` Headers: @@ -52,67 +47,46 @@ Request body: } ``` -## Outputs - -| Output | Meaning | -| --- | --- | -| `verification_id` | TrustSignal verification identifier returned by the API. | -| `status` | Verification status returned by the API. | -| `receipt_id` | Signed receipt identifier returned by the API. | -| `receipt_signature` | Receipt signature returned by the API. | - -If the API does not return a distinct verification identifier, the action exposes `verification_id` as a compatibility alias to `receipt_id`. - -## Example Workflow - -```yaml -name: Verify Artifact - -on: - push: - branches: [main] - -jobs: - verify: - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Build artifact - run: | - mkdir -p dist - echo "build output" > dist/app.txt - - - name: Verify artifact with TrustSignal - id: trustsignal - uses: trustsignal-dev/trustsignal-verify-artifact@v1 - with: - api_base_url: https://api.trustsignal.dev - api_key: ${{ secrets.TRUSTSIGNAL_API_KEY }} - artifact_path: dist/app.txt - source: github-actions - fail_on_mismatch: "true" - - - name: Record receipt - run: | - echo "Verification ID: ${{ steps.trustsignal.outputs.verification_id }}" - echo "Receipt ID: ${{ steps.trustsignal.outputs.receipt_id }}" - echo "Receipt signature: ${{ steps.trustsignal.outputs.receipt_signature }}" +Response fields used by the action: + +- `verificationId` +- `receiptId` +- `receiptSignature` +- `status` + +### `POST /api/v1/receipt/{receiptId}/verify` + +Request body: + +```json +{ + "artifact": { + "hash": "", + "algorithm": "sha256" + } +} ``` -## Current Limitations +Response fields: + +- `verified` +- `integrityVerified` +- `signatureVerified` +- `status` +- `receiptId` +- `receiptSignature` +- `storedHash` +- `recomputedHash` -- The local contract test uses a fetch mock rather than a live TrustSignal deployment. -- This package currently lives in a monorepo subdirectory, so it is not directly publishable to GitHub Marketplace. -- A production-facing integration test against a deployed TrustSignal environment is still pending. +## Security Boundary -## Next Steps Before Marketplace Publication +- The GitHub Action calls TrustSignal API only. +- Supabase is private backend persistence and is not a public integration surface. +- Service role credentials are backend-only and must never be exposed to clients. +- Artifact receipts are stored for later verification. +- Row Level Security is enabled on the artifact receipt table as defense in depth. + +## Current Limitations -- Extract the package into a dedicated public repository. -- Move `action.yml` to the repository root in that dedicated repository. -- Tag versioned releases and commit `dist/index.js` for each published release. -- Add a live integration test against a deployed TrustSignal verification endpoint. +- The repository includes a local smoke test, but a live deployed integration test remains pending. +- The public verification contract currently accepts `sha256` only. diff --git a/docs/security-summary.md b/docs/security-summary.md index 2cc6c36..63f7689 100644 --- a/docs/security-summary.md +++ b/docs/security-summary.md @@ -37,6 +37,10 @@ For the public `/api/v1/*` surface in this repository: - request validation and rate limiting are enforced at the API boundary - receipt revocation requires additional issuer authorization headers - later verification is available through a dedicated receipt verification route +- the GitHub Action calls TrustSignal API, not Supabase directly +- artifact receipts are persisted server-side behind the API boundary +- Supabase service-role credentials are backend-only and must never be exposed to browser or action code +- Row Level Security is enabled on the artifact receipt table as defense in depth Evaluator and demo flows are deliberate evaluator paths. They are designed to show the verification lifecycle safely before production integration. diff --git a/openapi.yaml b/openapi.yaml index e466577..529efe1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -24,8 +24,8 @@ paths: tags: [Verification] summary: Create a verification and receive a signed verification receipt description: | - Submit a verification request from an existing workflow. TrustSignal returns verification signals, - a signed verification receipt, and verifiable provenance metadata that can be used for later verification. + Submit a generic artifact verification request from an existing workflow. TrustSignal returns + a signed verification receipt and persists it for later verification. security: - ApiKeyAuth: [] requestBody: @@ -33,54 +33,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/VerificationRequest' + $ref: '#/components/schemas/ArtifactVerificationRequest' examples: default: summary: Verification request value: - bundleId: verification-2026-03-12-001 - transactionType: deed_transfer - ron: - provider: source-system - notaryId: NOTARY-EXAMPLE-01 - commissionState: IL - sealPayload: simulated-seal-payload - doc: - docHash: "0x8b7b2f52f2a2e19f8f3fe0d815d1c1d8d1e0d120e8cc60d1baf5e7a6f9d211aa" - policy: - profile: CONTROL_CC_001 - property: - parcelId: PARCEL-EXAMPLE-1001 - county: Cook - state: IL - timestamp: "2026-03-12T15:24:00.000Z" + artifact: + hash: 2f77668a9dfbf8d5847cf2d5d0370740e0c0601b4f061c1181f58c77c2b8f486 + algorithm: sha256 + source: + provider: github-actions + repository: TrustSignal-dev/TrustSignal-Verify-Artifact + workflow: Verify Build Artifact + runId: "12345" + commit: abc123def456 + actor: octocat + metadata: + artifactPath: dist/release.txt responses: '200': description: Verification completed and a signed verification receipt was issued. content: application/json: schema: - $ref: '#/components/schemas/VerificationResponse' + $ref: '#/components/schemas/ArtifactVerificationResponse' examples: default: summary: Verification response value: - receiptVersion: '2.0' - decision: ALLOW - reasons: - - receipt issued + verificationId: 2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a receiptId: 2c17d2f5-4de6-48c3-b22c-0b7ea9eb5c0a - receiptHash: "0x4e7f2ce9d3f7a8d3b0e4c9f2aa17fd59d6b4fda2d7b7b7d1cce8124d7ee39d04" - receiptSignature: - alg: EdDSA - kid: trustsignal-current - signature: eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ - anchor: - status: PENDING - subjectDigest: "0x8c0f95cda31274e7b61adfd1dd1e0c03a4b96f78d90da52d42fd93d9a38fc112" - subjectVersion: trustsignal.anchor_subject.v1 - revocation: - status: ACTIVE + receiptSignature: eyJleGFtcGxlIjoic2lnbmVkLXJlY2VpcHQifQ + status: verified '400': $ref: '#/components/responses/BadRequest' '401': @@ -154,19 +138,24 @@ paths: tags: [Lifecycle] summary: Check later verification status for a stored receipt description: | - Recompute receipt integrity and return the current verification status for later verification. - This endpoint does not accept a request body. + Compare a supplied artifact hash against a stored receipt and return the current verification status. security: - ApiKeyAuth: [] parameters: - $ref: '#/components/parameters/ReceiptId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ArtifactReceiptVerifyRequest' responses: '200': description: Receipt verification status returned. content: application/json: schema: - $ref: '#/components/schemas/VerificationStatus' + $ref: '#/components/schemas/ArtifactReceiptVerificationStatus' '400': $ref: '#/components/responses/BadRequest' '401': @@ -462,6 +451,120 @@ components: type: string format: date-time description: Caller-provided event timestamp. + ArtifactVerificationRequest: + type: object + additionalProperties: false + required: + - artifact + - source + properties: + artifact: + type: object + additionalProperties: false + required: + - hash + - algorithm + properties: + hash: + type: string + pattern: '^[A-Fa-f0-9]{64}$' + algorithm: + type: string + enum: [sha256] + source: + type: object + additionalProperties: false + required: + - provider + properties: + provider: + type: string + repository: + type: string + workflow: + type: string + runId: + type: string + commit: + type: string + actor: + type: string + metadata: + type: object + additionalProperties: false + properties: + artifactPath: + type: string + ArtifactVerificationResponse: + type: object + additionalProperties: false + required: + - verificationId + - receiptId + - receiptSignature + - status + properties: + verificationId: + type: string + format: uuid + receiptId: + type: string + format: uuid + receiptSignature: + type: string + status: + type: string + enum: [verified] + ArtifactReceiptVerifyRequest: + type: object + additionalProperties: false + required: + - artifact + properties: + artifact: + type: object + additionalProperties: false + required: + - hash + - algorithm + properties: + hash: + type: string + pattern: '^[A-Fa-f0-9]{64}$' + algorithm: + type: string + enum: [sha256] + ArtifactReceiptVerificationStatus: + type: object + additionalProperties: false + required: + - verified + - integrityVerified + - signatureVerified + - status + - receiptId + - receiptSignature + - storedHash + - recomputedHash + properties: + verified: + type: boolean + integrityVerified: + type: boolean + signatureVerified: + type: boolean + status: + type: string + enum: [verified, mismatch] + receiptId: + type: string + format: uuid + receiptSignature: + type: string + storedHash: + type: string + recomputedHash: + type: string VerificationResponse: type: object additionalProperties: true From 94c9b592fa0e616e4e51676d237ca3e9f8742776 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 03:27:26 -0500 Subject: [PATCH 20/25] feat: add public receipt verification surface and partner summary endpoint --- apps/api/src/artifact-verification.test.ts | 57 +++++++ apps/api/src/artifactReceipts.ts | 107 +++++++++++++ apps/api/src/request-validation.test.ts | 9 +- apps/api/src/server.ts | 46 +++++- apps/web/src/app/verify/[receiptId]/page.tsx | 116 ++++++++++++++ docs/integrations/github-action.md | 13 +- docs/integrations/public-verification.md | 65 ++++++++ docs/security-summary.md | 2 + openapi.yaml | 157 +++++++++++++++++-- 9 files changed, 557 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/app/verify/[receiptId]/page.tsx create mode 100644 docs/integrations/public-verification.md diff --git a/apps/api/src/artifact-verification.test.ts b/apps/api/src/artifact-verification.test.ts index 9fea071..83ddab4 100644 --- a/apps/api/src/artifact-verification.test.ts +++ b/apps/api/src/artifact-verification.test.ts @@ -70,6 +70,63 @@ describeWithDatabase('Generic artifact verification API', () => { ); expect(persistedRows).toHaveLength(1); + const publicReceipt = await app.inject({ + method: 'GET', + url: `/api/v1/receipt/${receipt.receiptId}` + }); + + expect(publicReceipt.statusCode).toBe(200); + expect(publicReceipt.json()).toMatchObject({ + receiptId: receipt.receiptId, + artifact: { + hash: artifactHash, + algorithm: 'sha256' + }, + source: { + provider: 'github-actions', + repository: 'TrustSignal-dev/TrustSignal-Verify-Artifact', + workflow: 'Verify Build Artifact', + runId: '12345', + commit: 'abc123def456', + actor: 'octocat' + }, + status: 'verified', + receiptSignature: { + alg: 'EdDSA' + } + }); + expect(publicReceipt.json().receiptSignature.signature).toBeUndefined(); + expect(publicReceipt.json().canonicalReceipt).toBeUndefined(); + expect(publicReceipt.json().verificationId).toBeUndefined(); + + const publicSummary = await app.inject({ + method: 'GET', + url: `/api/v1/receipt/${receipt.receiptId}/summary` + }); + + expect(publicSummary.statusCode).toBe(200); + expect(publicSummary.json()).toMatchObject({ + receiptId: receipt.receiptId, + status: 'verified', + integrityState: 'valid', + source: { + provider: 'github-actions', + repository: 'TrustSignal-dev/TrustSignal-Verify-Artifact', + workflow: 'Verify Build Artifact' + }, + display: { + label: 'TrustSignal Verified', + tone: 'success' + } + }); + + const missingReceipt = await app.inject({ + method: 'GET', + url: `/api/v1/receipt/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa` + }); + + expect(missingReceipt.statusCode).toBe(404); + const laterVerifyMatch = await app.inject({ method: 'POST', url: `/api/v1/receipt/${receipt.receiptId}/verify`, diff --git a/apps/api/src/artifactReceipts.ts b/apps/api/src/artifactReceipts.ts index 760c397..e2ec218 100644 --- a/apps/api/src/artifactReceipts.ts +++ b/apps/api/src/artifactReceipts.ts @@ -41,6 +41,46 @@ type ArtifactReceiptRow = { createdAt: Date; }; +export type ArtifactReceiptPublicView = { + receiptId: string; + artifact: { + hash: string; + algorithm: 'sha256'; + }; + source: { + provider: string; + repository?: string; + workflow?: string; + runId?: string; + commit?: string; + actor?: string; + }; + status: string; + createdAt: string; + receiptSignature: { + alg: string; + kid: string; + }; + verificationUrl?: string; +}; + +export type ArtifactReceiptSummaryView = { + receiptId: string; + status: string; + integrityState: 'valid' | 'check'; + issuedAt: string; + source: { + provider: string; + repository?: string; + workflow?: string; + }; + display: { + label: string; + tone: 'success' | 'warning'; + statement: string; + }; +}; + type SignedArtifactReceiptPayload = { receiptVersion: '1.0'; receiptId: string; @@ -163,6 +203,35 @@ function toSignedPayload(row: ArtifactReceiptRow): SignedArtifactReceiptPayload }; } +function toPublicSource(row: ArtifactReceiptRow) { + return { + provider: row.sourceProvider, + ...(row.repository ? { repository: row.repository } : {}), + ...(row.workflow ? { workflow: row.workflow } : {}), + ...(row.runId ? { runId: row.runId } : {}), + ...(row.commitSha ? { commit: row.commitSha } : {}), + ...(row.actor ? { actor: row.actor } : {}) + }; +} + +function toSummaryDisplay(status: string): ArtifactReceiptSummaryView['display'] { + if (status === 'verified') { + return { + label: 'TrustSignal Verified', + tone: 'success', + statement: + 'This artifact has a signed verification receipt and can be checked later for integrity drift.' + }; + } + + return { + label: 'TrustSignal Check Required', + tone: 'warning', + statement: + 'This receipt is available for review, but the current verification state should be checked before relying on it.' + }; +} + export async function issueArtifactReceipt( prisma: PrismaClient, securityConfig: SecurityConfig, @@ -261,6 +330,44 @@ export async function getArtifactReceiptById( return rows[0] || null; } +export function toArtifactReceiptPublicView( + row: ArtifactReceiptRow, + options: { verificationUrl?: string } = {} +): ArtifactReceiptPublicView { + return { + receiptId: row.receiptId, + artifact: { + hash: row.artifactHash, + algorithm: 'sha256' + }, + source: toPublicSource(row), + status: row.status, + createdAt: row.createdAt.toISOString(), + receiptSignature: { + alg: row.receiptSignatureAlg, + kid: row.receiptSignatureKid + }, + ...(options.verificationUrl ? { verificationUrl: options.verificationUrl } : {}) + }; +} + +export function toArtifactReceiptSummaryView( + row: ArtifactReceiptRow +): ArtifactReceiptSummaryView { + return { + receiptId: row.receiptId, + status: row.status, + integrityState: row.status === 'verified' ? 'valid' : 'check', + issuedAt: row.createdAt.toISOString(), + source: { + provider: row.sourceProvider, + ...(row.repository ? { repository: row.repository } : {}), + ...(row.workflow ? { workflow: row.workflow } : {}) + }, + display: toSummaryDisplay(row.status) + }; +} + export async function verifyArtifactReceiptById( prisma: PrismaClient, securityConfig: SecurityConfig, diff --git a/apps/api/src/request-validation.test.ts b/apps/api/src/request-validation.test.ts index 3664d96..99990f4 100644 --- a/apps/api/src/request-validation.test.ts +++ b/apps/api/src/request-validation.test.ts @@ -32,11 +32,16 @@ describe('Request validation hardening', () => { it('rejects invalid receiptId params', async () => { const res = await app.inject({ method: 'GET', - url: '/api/v1/receipt/invalid$id', - headers: { 'x-api-key': apiKey } + url: '/api/v1/receipt/invalid$id' + }); + + const summaryRes = await app.inject({ + method: 'GET', + url: '/api/v1/receipt/invalid$id/summary' }); expect(res.statusCode).toBe(expectedStatusCode); + expect(summaryRes.statusCode).toBe(expectedStatusCode); }); it('rejects request bodies on no-body mutation routes', async () => { diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 3bc4ca7..349b679 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -11,7 +11,10 @@ import { PrismaClient } from '@prisma/client'; import type { DeedParsed } from '../../../packages/public-contracts/dist/index.js'; import { + getArtifactReceiptById, issueArtifactReceipt, + toArtifactReceiptPublicView, + toArtifactReceiptSummaryView, verifyArtifactReceiptById } from './artifactReceipts.js'; import { toV2VerifyResponse } from './lib/v2ReceiptMapper.js'; @@ -411,6 +414,18 @@ function normalizeForwardedProto(value: string | string[] | undefined): string | return first || null; } +function buildPublicVerificationUrl(request: { + headers: Record; + protocol?: string; +}, receiptId: string): string | null { + const forwardedProto = normalizeForwardedProto(request.headers['x-forwarded-proto']); + const hostHeader = request.headers['x-forwarded-host'] || request.headers.host; + const host = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader; + if (!host) return null; + const protocol = forwardedProto || request.protocol || 'https'; + return `${protocol}://${host}/verify/${receiptId}`; +} + function databaseUrlHasRequiredSslMode(databaseUrl: string | undefined): boolean { if (!databaseUrl) return false; try { @@ -503,6 +518,7 @@ export async function buildServer(options: BuildServerOptions = {}) { timeWindow: securityConfig.rateLimitWindow, keyGenerator: getApiRateLimitKey }; + const requireReadScope = requireApiKeyScope(securityConfig, 'read'); app.addHook('onRequest', async (request) => { const timedRequest = request as typeof request & { @@ -783,11 +799,26 @@ export async function buildServer(options: BuildServerOptions = {}) { }); app.get('/api/v1/receipt/:receiptId', { - preHandler: [requireApiKeyScope(securityConfig, 'read')], config: { rateLimit: perApiKeyRateLimit } }, async (request, reply) => { const receiptId = parseReceiptIdParam(request, reply); if (!receiptId) return; + const artifactReceipt = await getArtifactReceiptById(prisma, receiptId); + if (artifactReceipt) { + return reply.send( + toArtifactReceiptPublicView(artifactReceipt, { + verificationUrl: buildPublicVerificationUrl(request, receiptId) || undefined + }) + ); + } + + if (!request.headers['x-api-key']) { + return reply.code(404).send({ error: 'Receipt not found' }); + } + + await requireReadScope(request, reply); + if (reply.sent) return; + const storedReceipt = await verificationEngine.getReceipt(receiptId); if (!storedReceipt) { return reply.code(404).send({ error: 'Receipt not found' }); @@ -817,6 +848,19 @@ export async function buildServer(options: BuildServerOptions = {}) { }); }); + app.get('/api/v1/receipt/:receiptId/summary', { + config: { rateLimit: perApiKeyRateLimit } + }, async (request, reply) => { + const receiptId = parseReceiptIdParam(request, reply); + if (!receiptId) return; + const artifactReceipt = await getArtifactReceiptById(prisma, receiptId); + if (!artifactReceipt) { + return reply.code(404).send({ error: 'Receipt not found' }); + } + + return reply.send(toArtifactReceiptSummaryView(artifactReceipt)); + }); + app.get('/api/v1/receipt/:receiptId/pdf', { preHandler: [requireApiKeyScope(securityConfig, 'read')], config: { rateLimit: perApiKeyRateLimit } diff --git a/apps/web/src/app/verify/[receiptId]/page.tsx b/apps/web/src/app/verify/[receiptId]/page.tsx new file mode 100644 index 0000000..e77aef7 --- /dev/null +++ b/apps/web/src/app/verify/[receiptId]/page.tsx @@ -0,0 +1,116 @@ +const API_BASE = + process.env.API_BASE || + process.env.NEXT_PUBLIC_API_BASE || + 'http://localhost:3001'; + +type ReceiptInspectorResponse = { + receiptId: string; + artifact: { + hash: string; + algorithm: string; + }; + source: { + provider: string; + repository?: string; + workflow?: string; + runId?: string; + commit?: string; + actor?: string; + }; + status: string; + createdAt: string; + receiptSignature: { + alg: string; + kid: string; + }; +}; + +async function loadReceipt(receiptId: string): Promise { + const response = await fetch(`${API_BASE}/api/v1/receipt/${receiptId}`, { + cache: 'no-store' + }); + + if (response.status === 404) { + return null; + } + + if (!response.ok) { + throw new Error('Unable to load receipt'); + } + + return (await response.json()) as ReceiptInspectorResponse; +} + +function renderValue(value: string | undefined) { + return value || 'Not provided'; +} + +export default async function PublicReceiptInspectorPage({ + params +}: { + params: { receiptId: string }; +}) { + const detail = await loadReceipt(params.receiptId); + + if (!detail) { + return ( +
+
+

Receipt not found

+

+ No public TrustSignal receipt was found for this identifier. +

+
+
+ ); + } + + return ( +
+
+

Public verification inspector

+

TrustSignal receipt {detail.receiptId}

+

+ TrustSignal provides verification signals and signed receipts; it does not + make legal determinations. +

+
+ +
+

Status

+

+ {detail.status} +

+

+ Issued {new Date(detail.createdAt).toLocaleString()} +

+

+ This receipt can be referenced later to verify whether the same artifact + hash still matches the stored verification record. +

+
+ +
+

Artifact

+

{detail.artifact.hash}

+

Algorithm: {detail.artifact.algorithm}

+
+ +
+

Source

+

Provider: {detail.source.provider}

+

Repository: {renderValue(detail.source.repository)}

+

Workflow: {renderValue(detail.source.workflow)}

+

Run ID: {renderValue(detail.source.runId)}

+

Commit: {renderValue(detail.source.commit)}

+

Actor: {renderValue(detail.source.actor)}

+
+ +
+

Receipt signature

+

Algorithm: {detail.receiptSignature.alg}

+

Key ID: {detail.receiptSignature.kid}

+
+
+ ); +} diff --git a/docs/integrations/github-action.md b/docs/integrations/github-action.md index 583855f..02ee084 100644 --- a/docs/integrations/github-action.md +++ b/docs/integrations/github-action.md @@ -12,7 +12,8 @@ The GitHub Action does not connect to Supabase directly. TrustSignal persists re 2. The action calls `POST /api/v1/verify` on `api.trustsignal.dev`. 3. TrustSignal validates the request, authenticates the caller, issues a signed receipt, and persists the receipt server-side. 4. The action stores `receiptId` and `receiptSignature` for later verification or audit use. -5. A later workflow can call `POST /api/v1/receipt/{receiptId}/verify` with an artifact hash to confirm integrity. +5. Public consumers can inspect the stored receipt through `GET /api/v1/receipt/{receiptId}` or render a compact badge from `GET /api/v1/receipt/{receiptId}/summary`. +6. A later workflow can call `POST /api/v1/receipt/{receiptId}/verify` with an artifact hash to confirm integrity. ## Public API Contract @@ -54,6 +55,14 @@ Response fields used by the action: - `receiptSignature` - `status` +### `GET /api/v1/receipt/{receiptId}` + +This public-safe endpoint returns a compact inspection view for artifact receipts. It is intended for receipt drill-down pages and audit references. + +### `GET /api/v1/receipt/{receiptId}/summary` + +This public-safe endpoint returns a compact display payload for trust centers, evidence panels, and partner dashboards. + ### `POST /api/v1/receipt/{receiptId}/verify` Request body: @@ -85,6 +94,8 @@ Response fields: - Service role credentials are backend-only and must never be exposed to clients. - Artifact receipts are stored for later verification. - Row Level Security is enabled on the artifact receipt table as defense in depth. +- Public lookup and summary endpoints are read-only and return safe receipt fields only. +- Later verification remains behind TrustSignal API authentication. ## Current Limitations diff --git a/docs/integrations/public-verification.md b/docs/integrations/public-verification.md new file mode 100644 index 0000000..d7ba70c --- /dev/null +++ b/docs/integrations/public-verification.md @@ -0,0 +1,65 @@ +# TrustSignal Public Verification + +## Why Public Verification Matters + +TrustSignal receipts are designed to travel with an artifact after the initial workflow run. A team can issue a signed verification receipt once, store the receipt identifier with its evidence record, and later let auditors, buyers, or partner platforms inspect the receipt without exposing internal systems. + +This makes TrustSignal useful in trust-center views, evidence panels, and partner review workflows where the downstream user needs a compact integrity signal rather than internal engine details. + +## Receipt Lookup Flow + +1. A workflow issues a signed receipt through `POST /api/v1/verify`. +2. The caller stores `receiptId` with its evidence or build record. +3. A public or partner-facing surface retrieves the safe receipt view with `GET /api/v1/receipt/{receiptId}`. + +The public lookup response is artifact-oriented and omits internal scoring, signing secrets, and private service details. + +## Later Verification Flow + +1. A trusted integration submits an artifact hash to `POST /api/v1/receipt/{receiptId}/verify`. +2. TrustSignal compares the supplied hash with the stored artifact hash. +3. The API returns whether integrity and signature checks still pass. + +This route remains authenticated. Public inspection is read-only; active verification stays behind the TrustSignal API boundary. + +## Partner Summary Flow + +`GET /api/v1/receipt/{receiptId}/summary` returns a compact verification badge payload for trust centers, compliance dashboards, and partner evidence panels. + +It is designed for simple display logic: + +- `status` +- `integrityState` +- `issuedAt` +- source summary +- a ready-to-render `display` object + +## Example Partner Uses + +### Drata-style evidence view + +Store `receiptId` alongside a control evidence record. When an auditor opens the evidence detail, the platform can fetch `/summary` and render a compact TrustSignal integrity badge next to the artifact metadata. + +### Vanta-style evidence view + +Attach the receipt to a control test result. Use `/receipt/{receiptId}` for drill-down and `/summary` for the evidence list row. + +### Public trust center or vendor review + +Expose a receipt inspector link such as `/verify/{receiptId}`. Buyers can review the signed receipt metadata without gaining access to private systems or backend persistence. + +## Verification Badge Example + +```json +{ + "receiptId": "8fb78fc6-2763-4e63-9f65-67da2f9f6d98", + "status": "verified", + "integrityState": "valid", + "issuedAt": "2026-03-13T09:06:47.000Z", + "display": { + "label": "TrustSignal Verified", + "tone": "success", + "statement": "This artifact has a signed verification receipt and can be checked later for integrity drift." + } +} +``` diff --git a/docs/security-summary.md b/docs/security-summary.md index 63f7689..5e562a2 100644 --- a/docs/security-summary.md +++ b/docs/security-summary.md @@ -37,10 +37,12 @@ For the public `/api/v1/*` surface in this repository: - request validation and rate limiting are enforced at the API boundary - receipt revocation requires additional issuer authorization headers - later verification is available through a dedicated receipt verification route +- public receipt inspection is available through `GET /api/v1/receipt/{receiptId}` and `GET /api/v1/receipt/{receiptId}/summary` for artifact receipts backed by unguessable receipt IDs - the GitHub Action calls TrustSignal API, not Supabase directly - artifact receipts are persisted server-side behind the API boundary - Supabase service-role credentials are backend-only and must never be exposed to browser or action code - Row Level Security is enabled on the artifact receipt table as defense in depth +- active later verification stays behind authenticated API calls even when public inspection is enabled Evaluator and demo flows are deliberate evaluator paths. They are designed to show the verification lifecycle safely before production integration. diff --git a/openapi.yaml b/openapi.yaml index 529efe1..73c4c6a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -78,27 +78,49 @@ paths: /api/v1/receipt/{receiptId}: get: tags: [Receipts] - summary: Retrieve a stored verification receipt + summary: Retrieve a public-safe artifact receipt view description: | - Return the stored receipt view for a previously created verification, - including receipt metadata, the canonical receipt payload, and a PDF URL. - security: - - ApiKeyAuth: [] + Return a compact inspection view for an artifact receipt. The response is safe for + external display and omits internal scoring, raw signatures, and backend details. + Artifact receipt identifiers are unguessable UUIDs, which allows read-only inspection + without exposing private persistence. Legacy non-artifact receipts still require read-scoped auth. + security: [] parameters: - $ref: '#/components/parameters/ReceiptId' responses: '200': - description: Stored receipt returned. + description: Public artifact receipt view returned. content: application/json: schema: - $ref: '#/components/schemas/VerificationReceipt' + $ref: '#/components/schemas/PublicArtifactReceipt' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '503': + $ref: '#/components/responses/ServiceUnavailable' + /api/v1/receipt/{receiptId}/summary: + get: + tags: [Receipts] + summary: Retrieve a compact partner-ready receipt summary + description: | + Return a stable summary payload that partner platforms can render as a verification badge + or evidence-panel status without understanding TrustSignal internals. + security: [] + parameters: + - $ref: '#/components/parameters/ReceiptId' + responses: + '200': + description: Compact verification summary returned. + content: + application/json: + schema: + $ref: '#/components/schemas/PublicArtifactReceiptSummary' '400': $ref: '#/components/responses/BadRequest' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': @@ -565,6 +587,119 @@ components: type: string recomputedHash: type: string + PublicArtifactReceipt: + type: object + additionalProperties: false + required: + - receiptId + - artifact + - source + - status + - createdAt + - receiptSignature + properties: + receiptId: + type: string + format: uuid + artifact: + type: object + additionalProperties: false + required: + - hash + - algorithm + properties: + hash: + type: string + pattern: '^[A-Fa-f0-9]{64}$' + algorithm: + type: string + enum: [sha256] + source: + type: object + additionalProperties: false + required: + - provider + properties: + provider: + type: string + repository: + type: string + workflow: + type: string + runId: + type: string + commit: + type: string + actor: + type: string + status: + type: string + createdAt: + type: string + format: date-time + receiptSignature: + type: object + additionalProperties: false + required: + - alg + - kid + properties: + alg: + type: string + kid: + type: string + verificationUrl: + type: string + format: uri + PublicArtifactReceiptSummary: + type: object + additionalProperties: false + required: + - receiptId + - status + - integrityState + - issuedAt + - source + - display + properties: + receiptId: + type: string + format: uuid + status: + type: string + integrityState: + type: string + enum: [valid, check] + issuedAt: + type: string + format: date-time + source: + type: object + additionalProperties: false + required: + - provider + properties: + provider: + type: string + repository: + type: string + workflow: + type: string + display: + type: object + additionalProperties: false + required: + - label + - tone + - statement + properties: + label: + type: string + tone: + type: string + enum: [success, warning] + statement: + type: string VerificationResponse: type: object additionalProperties: true From 559ae2b534f46dcf9aab552ef5de0648f77fccf5 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 03:38:49 -0500 Subject: [PATCH 21/25] security: audit public repo exposure and harden public verification surfaces --- apps/api/src/server.ts | 10 ++++- apps/api/src/supabaseAdmin.ts | 4 +- .../supabase-db-security-2026-02-27.md | 10 ++--- docs/security/public-repo-safety.md | 44 +++++++++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 docs/security/public-repo-safety.md diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 349b679..feda9af 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -437,6 +437,14 @@ function databaseUrlHasRequiredSslMode(databaseUrl: string | undefined): boolean } } +function summarizeDatabaseInitError(error: unknown): string { + if (!error) return 'database_initialization_failed'; + if (error instanceof Error && error.name) { + return `${error.name}: database_initialization_failed`; + } + return 'database_initialization_failed'; +} + function requireProductionVerifierConfig(env: NodeJS.ProcessEnv = process.env): void { if ((env.NODE_ENV || 'development') !== 'production') { return; @@ -563,7 +571,7 @@ export async function buildServer(options: BuildServerOptions = {}) { await ensureDatabase(prisma); } catch (error) { databaseReady = false; - databaseInitError = error instanceof Error ? error.message : 'database_initialization_failed'; + databaseInitError = summarizeDatabaseInitError(error); app.log.error({ err: error }, 'database initialization failed; non-DB routes remain available'); } diff --git a/apps/api/src/supabaseAdmin.ts b/apps/api/src/supabaseAdmin.ts index 716e68a..7450478 100644 --- a/apps/api/src/supabaseAdmin.ts +++ b/apps/api/src/supabaseAdmin.ts @@ -8,9 +8,7 @@ function readEnv(name: string): string { } export function getServerOnlySupabaseConfig(): ServerOnlySupabaseConfig | null { - const url = - readEnv('SUPABASE_URL') || - readEnv('NEXT_PUBLIC_SUPABASE_URL'); + const url = readEnv('SUPABASE_URL'); const serviceRoleKey = readEnv('SUPABASE_SERVICE_ROLE_KEY') || readEnv('SUPABASE_SECRET_KEY'); diff --git a/docs/evidence/staging/supabase-db-security-2026-02-27.md b/docs/evidence/staging/supabase-db-security-2026-02-27.md index 58f445d..d1bf504 100644 --- a/docs/evidence/staging/supabase-db-security-2026-02-27.md +++ b/docs/evidence/staging/supabase-db-security-2026-02-27.md @@ -1,12 +1,12 @@ # Supabase DB Security Evidence - Captured at (UTC): 2026-02-28T00:41:40Z -- Supabase project ref: `bwjyvakfrnmaawztasxu` -- DB target: `aws-1-us-east-2.pooler.supabase.com:5432/postgres` +- Supabase project ref: `[redacted]` +- DB target: `[redacted]` ## 1. SSL Enforcement (Provider Control) Command: -`supabase --experimental ssl-enforcement get --project-ref bwjyvakfrnmaawztasxu` +`supabase --experimental ssl-enforcement get --project-ref [redacted]` Output: ```text @@ -15,7 +15,7 @@ SSL is being enforced. ## 2. Encryption-at-Rest Control Presence (Redacted) Command: -`supabase --experimental encryption get-root-key --project-ref bwjyvakfrnmaawztasxu` +`supabase --experimental encryption get-root-key --project-ref [redacted]` Redacted output summary: ```text @@ -26,7 +26,7 @@ Interpretation: root encryption key is present; full key material intentionally ## 3. Live DB TLS Session Proof Command: -`PGPASSWORD='***' psql "host=aws-1-us-east-2.pooler.supabase.com port=5432 dbname=postgres user=postgres.bwjyvakfrnmaawztasxu sslmode=require connect_timeout=8" -Atc "select 'ssl='||ssl::text||',version='||version||',cipher='||cipher from pg_stat_ssl where pid=pg_backend_pid();"` +`PGPASSWORD='***' psql "host=[redacted] port=5432 dbname=postgres user=[redacted] sslmode=require connect_timeout=8" -Atc "select 'ssl='||ssl::text||',version='||version||',cipher='||cipher from pg_stat_ssl where pid=pg_backend_pid();"` Output: ```text diff --git a/docs/security/public-repo-safety.md b/docs/security/public-repo-safety.md new file mode 100644 index 0000000..e1ea39d --- /dev/null +++ b/docs/security/public-repo-safety.md @@ -0,0 +1,44 @@ +# Public Repo Safety + +TrustSignal is a public repository. It is intended to expose the integration-facing verification surface, public-safe documentation, and example workflows for signed verification receipts. + +## Intentionally Public + +- public API contracts +- integration examples +- public-safe receipt lookup and summary responses +- generic verification lifecycle documentation +- placeholder environment examples + +## Must Never Be Committed + +- live secrets or tokens +- service-role or admin credentials +- database passwords or full connection strings +- signing private keys or raw key exports +- private evidence, private customer data, or raw production payloads + +## Supabase Boundary + +Supabase persistence is backend-only. Service-role credentials are used only by the TrustSignal API server and must never appear in browser code, GitHub Actions workflows, or public client examples. + +The intended architecture is: + +`Client or GitHub Action -> TrustSignal API -> Supabase` + +## Public Verification Surface + +Public receipt endpoints expose only safe receipt metadata needed for inspection: + +- receipt identifier +- artifact hash and algorithm +- source metadata +- verification status +- issued timestamp +- safe receipt-signature metadata such as algorithm and key identifier + +They do not expose signing key material, service-role credentials, private infrastructure details, or internal verification engine state. + +## Private Material Stays Outside The Repo + +Operational secrets, private evidence, full environment values, and internal infrastructure details must remain outside the public repository and outside public docs. From 330318d2e38f63b6afb7f94b2abd4b0c56f61590 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Fri, 13 Mar 2026 04:59:42 -0500 Subject: [PATCH 22/25] docs: polish marketplace readme and remove stale release language --- .../trustsignal-verify-artifact/README.md | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/github-actions/trustsignal-verify-artifact/README.md b/github-actions/trustsignal-verify-artifact/README.md index 9c0e0a5..496f6ae 100644 --- a/github-actions/trustsignal-verify-artifact/README.md +++ b/github-actions/trustsignal-verify-artifact/README.md @@ -1,13 +1,13 @@ # TrustSignal Verify Artifact -Verify build artifacts in CI, issue signed verification receipts, and preserve provenance for later verification and audit workflows. +Verify release artifacts in CI, issue signed verification receipts, and preserve provenance for downstream verification and audit workflows. [![License: MIT](https://img.shields.io/badge/license-MIT-informational)](LICENSE) [![Node.js](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](package.json) -`TrustSignal Verify Artifact` is a JavaScript GitHub Action for teams that want a clean verification checkpoint inside CI/CD. It hashes a build artifact or accepts a precomputed SHA-256 digest, submits that identity to TrustSignal, and returns receipt metadata your pipeline can persist, attach to release records, or feed into later verification workflows. +`TrustSignal Verify Artifact` is a JavaScript GitHub Action for teams that need a reliable verification checkpoint inside CI/CD. It hashes a build artifact or accepts a precomputed SHA-256 digest, submits that artifact identity to TrustSignal, and returns receipt metadata that can be retained with release records, provenance evidence, and downstream audit workflows. -TrustSignal is built for artifact integrity, signed receipts, verifiable provenance, and downstream auditability without exposing internal verification engine details. +TrustSignal is designed for artifact integrity, signed verification receipts, verifiable provenance, and audit-ready release controls. ## Features @@ -26,7 +26,7 @@ TrustSignal is built for artifact integrity, signed receipts, verifiable provena ## Quick Start -1. Add a TrustSignal API key to GitHub Actions secrets. +1. Add `TRUSTSIGNAL_API_BASE_URL` and `TRUSTSIGNAL_API_KEY` to GitHub Actions secrets. 2. Call the action with either `artifact_path` or `artifact_hash`. 3. Capture `receipt_id` and `receipt_signature` in downstream steps. 4. Store receipt metadata anywhere you need later verification or audit evidence. @@ -48,7 +48,7 @@ Provide exactly one of `artifact_path` or `artifact_hash`. | Output | Description | | --- | --- | -| `verification_id` | Verification identifier returned by TrustSignal. Falls back to `receipt_id` when the API does not return a separate value. | +| `verification_id` | Verification identifier returned by TrustSignal. For compatibility, this aliases `receipt_id` when the API does not return a separate verification identifier. | | `status` | Normalized verification status returned by the API. | | `receipt_id` | Signed receipt identifier returned by TrustSignal. | | `receipt_signature` | Signed receipt signature returned by TrustSignal. | @@ -83,7 +83,7 @@ jobs: id: trustsignal uses: trustsignal-dev/trustsignal-verify-artifact@v1 with: - api_base_url: https://api.trustsignal.dev + api_base_url: ${{ secrets.TRUSTSIGNAL_API_BASE_URL }} api_key: ${{ secrets.TRUSTSIGNAL_API_KEY }} artifact_path: dist/release.txt source: github-actions @@ -116,7 +116,7 @@ jobs: id: trustsignal uses: trustsignal-dev/trustsignal-verify-artifact@v1 with: - api_base_url: https://api.trustsignal.dev + api_base_url: ${{ secrets.TRUSTSIGNAL_API_BASE_URL }} api_key: ${{ secrets.TRUSTSIGNAL_API_KEY }} artifact_hash: 2f77668a9dfbf8d5847cf2d5d0370740e0c0601b4f061c1181f58c77c2b8f486 source: github-actions @@ -164,13 +164,13 @@ GitHub workflow context is added automatically when those environment variables ## Why TrustSignal -TrustSignal helps teams add a verification layer to CI/CD without exposing proprietary implementation details in every workflow. The action focuses on artifact identity, signed receipts, provenance continuity, and later verification so integrity signals can travel with the software lifecycle. +TrustSignal gives security and release teams a consistent way to verify artifact identity inside CI/CD while preserving signed evidence for later validation. The action is built to support integrity controls, provenance continuity, and audit-ready release workflows without forcing teams to reimplement verification logic in every pipeline. ## Current Limitations -- The local test path uses a fetch mock rather than a live TrustSignal deployment. -- This package is extractable today, but Marketplace publication still requires a dedicated public repository root. -- A production-facing integration test against a deployed TrustSignal API is still pending. +- Local validation uses a fetch mock rather than a live TrustSignal deployment. +- GitHub Marketplace publication requires this action to be published from a dedicated public repository root with `action.yml` at the top level. +- Live end-to-end validation against a deployed TrustSignal API should remain part of the release process. ## Local Validation @@ -193,29 +193,17 @@ npm run validate:local ## Versioning Guidance - Follow semantic versioning. -- Publish immutable release tags such as `v0.1.0`, `v0.2.0`, and `v1.0.0`. +- Publish immutable release tags for each shipped version. - Maintain a major tag such as `v1` for stable consumers. -## Release Guidance +## Release Checklist - Commit the built `dist/index.js` artifact with every release. - Create signed or otherwise controlled release tags according to your release process. - Update documentation when the public API contract or output mapping changes. -- Marketplace publication requires a public repository, a root-level `action.yml`, and release tags. ## Roadmap - Add a live integration test against a deployed TrustSignal verification endpoint - Publish the action from a dedicated public repository root - Add example workflows for release pipelines and provenance retention patterns - -## Suggested GitHub Topics - -- `github-action` -- `devsecops` -- `ci-cd` -- `artifact-integrity` -- `supply-chain-security` -- `compliance` -- `provenance` -- `verification` From 3be26cba96ad57aee8a4d3a817f6d29d330ac49f Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Sun, 15 Mar 2026 20:16:34 -0500 Subject: [PATCH 23/25] Fix API route aliases in Vercel config --- vercel.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vercel.json b/vercel.json index 14c7c36..72ec604 100644 --- a/vercel.json +++ b/vercel.json @@ -16,6 +16,18 @@ { "src": "/api/(.*)", "dest": "api/[...path].ts" + }, + { + "src": "/v1/(.*)", + "dest": "api/[...path].ts" + }, + { + "src": "/version", + "dest": "api/[...path].ts" + }, + { + "src": "/health", + "dest": "api/[...path].ts" } ], "headers": [ From e9cc7e7797a33d2a7e97b306f7e36a7357f11f35 Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Sun, 15 Mar 2026 20:17:58 -0500 Subject: [PATCH 24/25] Remove stale duplicate Vercel config file --- vercel.api.json | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 vercel.api.json diff --git a/vercel.api.json b/vercel.api.json deleted file mode 100644 index 332dc69..0000000 --- a/vercel.api.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "version": 2, - "buildCommand": "npm --workspace packages/core run build && npm --workspace apps/api run build", - "builds": [ - { - "src": "api/[...path].ts", - "use": "@vercel/node" - } - ], - "routes": [ - { - "src": "/api/(.*)", - "dest": "api/[...path].ts" - } - ], - "headers": [ - { - "source": "/(.*)", - "headers": [ - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "Referrer-Policy", - "value": "strict-origin-when-cross-origin" - }, - { - "key": "Permissions-Policy", - "value": "camera=(), microphone=(), geolocation=()" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=31536000; includeSubDomains; preload" - } - ] - } - ] -} From 5bd44d862aa233f8b3cdd89c45533a18e01d5e7f Mon Sep 17 00:00:00 2001 From: chrismaz11 Date: Sun, 15 Mar 2026 20:25:27 -0500 Subject: [PATCH 25/25] Make secret scan workflow not require GITLEAKS_LICENSE --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 238a76d..39be64f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,6 +97,29 @@ jobs: - name: Run unit/integration tests with coverage run: npx vitest run --coverage + web-build: + runs-on: ubuntu-latest + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + TRUSTSIGNAL_JWT_SECRET: ${{ secrets.TRUSTSIGNAL_JWT_SECRET }} + TRUSTSIGNAL_JWT_SECRETS: ${{ secrets.TRUSTSIGNAL_JWT_SECRETS }} + POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build releasable web surface + run: npm --workspace apps/web run build + signed-receipt-smoke: runs-on: ubuntu-latest services: @@ -156,8 +179,6 @@ jobs: secret-scan: runs-on: ubuntu-latest - env: - GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} steps: - name: Checkout uses: actions/checkout@v6