diff --git a/SECURITY_CHECKLIST.md b/SECURITY_CHECKLIST.md index 9633521..1e83e4b 100644 --- a/SECURITY_CHECKLIST.md +++ b/SECURITY_CHECKLIST.md @@ -45,7 +45,7 @@ | # | Requirement | Status | Evidence | | --- | ------------------------------------------ | ------ | -------------------------------------------------------- | -| 4.1 | Keccak-256 for document hashing | ✅ | `keccak256Buffer` from `@deed-shield/core`. | +| 4.1 | Keccak-256 for document hashing | ✅ | `keccak256Buffer` from `@trustsignal/core`. | | 4.2 | Receipt hash verification | ✅ | `POST /receipt/:id/verify` recomputes hash. | | 4.3 | JWT receipts have expiration | ✅ | Enforced in core receipt builder. | | 4.4 | Private keys never in code or config files | ✅ | Only via `PRIVATE_KEY` env var, never imported directly. | diff --git a/apps/api/SETUP.md b/apps/api/SETUP.md index 9074338..f44aa14 100644 --- a/apps/api/SETUP.md +++ b/apps/api/SETUP.md @@ -67,8 +67,8 @@ npx prisma db seed ```bash docker run -d \ - --name deed-shield-pg \ - -e POSTGRES_DB=deed_shield \ + --name trustsignal-pg \ + -e POSTGRES_DB=trustsignal \ -e POSTGRES_PASSWORD=localdev \ -p 5432:5432 \ postgres:16-alpine diff --git a/apps/api/monitoring/grafana-dashboard.json b/apps/api/monitoring/grafana-dashboard.json index 9a2ebb0..2fef532 100644 --- a/apps/api/monitoring/grafana-dashboard.json +++ b/apps/api/monitoring/grafana-dashboard.json @@ -58,7 +58,7 @@ "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "1 - (sum(rate(deedshield_http_errors_total{status_code=~\"5..\"}[5m])) / sum(rate(trustsignal_http_requests_total[5m])))", + "expr": "1 - (sum(rate(trustsignal_http_errors_total{status_code=~\"5..\"}[5m])) / sum(rate(trustsignal_http_requests_total[5m])))", "legendFormat": "availability" } ] @@ -87,7 +87,7 @@ "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "histogram_quantile(0.99, sum(rate(deedshield_verify_duration_seconds_bucket[5m])) by (le))", + "expr": "histogram_quantile(0.99, sum(rate(trustsignal_verify_duration_seconds_bucket[5m])) by (le))", "legendFormat": "p99" } ] @@ -116,7 +116,7 @@ "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "histogram_quantile(0.99, sum(rate(deedshield_receipt_lookup_duration_seconds_bucket[5m])) by (le))", + "expr": "histogram_quantile(0.99, sum(rate(trustsignal_receipt_lookup_duration_seconds_bucket[5m])) by (le))", "legendFormat": "p99" } ] @@ -145,7 +145,7 @@ "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum(rate(deedshield_http_errors_total{status_code=~\"5..\"}[5m])) / sum(rate(trustsignal_http_requests_total[5m]))", + "expr": "sum(rate(trustsignal_http_errors_total{status_code=~\"5..\"}[5m])) / sum(rate(trustsignal_http_requests_total[5m]))", "legendFormat": "error rate" } ] @@ -184,7 +184,7 @@ "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum(rate(deedshield_http_errors_total[1m])) by (status_code, route)", + "expr": "sum(rate(trustsignal_http_errors_total[1m])) by (status_code, route)", "legendFormat": "{{ status_code }} {{ route }}" } ] @@ -206,15 +206,15 @@ "type": "timeseries", "targets": [ { - "expr": "histogram_quantile(0.50, sum(rate(deedshield_verify_duration_seconds_bucket[5m])) by (le))", + "expr": "histogram_quantile(0.50, sum(rate(trustsignal_verify_duration_seconds_bucket[5m])) by (le))", "legendFormat": "p50" }, { - "expr": "histogram_quantile(0.95, sum(rate(deedshield_verify_duration_seconds_bucket[5m])) by (le))", + "expr": "histogram_quantile(0.95, sum(rate(trustsignal_verify_duration_seconds_bucket[5m])) by (le))", "legendFormat": "p95" }, { - "expr": "histogram_quantile(0.99, sum(rate(deedshield_verify_duration_seconds_bucket[5m])) by (le))", + "expr": "histogram_quantile(0.99, sum(rate(trustsignal_verify_duration_seconds_bucket[5m])) by (le))", "legendFormat": "p99" } ] @@ -251,7 +251,7 @@ "type": "timeseries", "targets": [ { - "expr": "sum(rate(deedshield_receipts_issued_total[5m])) by (decision)", + "expr": "sum(rate(trustsignal_receipts_issued_total[5m])) by (decision)", "legendFormat": "{{ decision }}" } ] @@ -266,7 +266,7 @@ "type": "timeseries", "targets": [ { - "expr": "sum(rate(deedshield_receipt_verifications_total[5m])) by (outcome)", + "expr": "sum(rate(trustsignal_receipt_verifications_total[5m])) by (outcome)", "legendFormat": "{{ outcome }}" } ] @@ -281,7 +281,7 @@ "type": "timeseries", "targets": [ { - "expr": "histogram_quantile(0.99, sum(rate(deedshield_anchor_duration_seconds_bucket[10m])) by (le, chain))", + "expr": "histogram_quantile(0.99, sum(rate(trustsignal_anchor_duration_seconds_bucket[10m])) by (le, chain))", "legendFormat": "p99 {{ chain }}" } ] @@ -303,7 +303,7 @@ "type": "timeseries", "targets": [ { - "expr": "rate(deedshield_api_process_cpu_seconds_total[1m])", + "expr": "rate(trustsignal_api_process_cpu_seconds_total[1m])", "legendFormat": "cpu" } ] @@ -318,11 +318,11 @@ "type": "timeseries", "targets": [ { - "expr": "deedshield_api_nodejs_heap_size_used_bytes", + "expr": "trustsignal_api_nodejs_heap_size_used_bytes", "legendFormat": "heap used" }, { - "expr": "deedshield_api_nodejs_heap_size_total_bytes", + "expr": "trustsignal_api_nodejs_heap_size_total_bytes", "legendFormat": "heap total" } ] diff --git a/apps/api/package.json b/apps/api/package.json index 367686c..e1d09d7 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,5 +1,5 @@ { - "name": "@deed-shield/api", + "name": "@trustsignal/api", "version": "0.2.0", "private": true, "type": "commonjs", @@ -15,7 +15,7 @@ "test": "vitest run" }, "dependencies": { - "@deed-shield/core": "file:../../packages/core", + "@trustsignal/core": "file:../../packages/core", "@fastify/cors": "^11.2.0", "@fastify/rate-limit": "^10.3.0", "@prisma/client": "^5.17.0", diff --git a/apps/api/prisma/migrations/20260410000000_add_receipt_indexes/migration.sql b/apps/api/prisma/migrations/20260410000000_add_receipt_indexes/migration.sql new file mode 100644 index 0000000..daa208e --- /dev/null +++ b/apps/api/prisma/migrations/20260410000000_add_receipt_indexes/migration.sql @@ -0,0 +1,10 @@ +-- Add performance indexes to Receipt table for high-traffic query patterns + +CREATE INDEX IF NOT EXISTS "Receipt_createdAt_idx" + ON "Receipt"("createdAt"); + +CREATE INDEX IF NOT EXISTS "Receipt_revoked_createdAt_idx" + ON "Receipt"("revoked", "createdAt"); + +CREATE INDEX IF NOT EXISTS "Receipt_policyProfile_createdAt_idx" + ON "Receipt"("policyProfile", "createdAt"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index bf6865d..bead399 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -54,6 +54,10 @@ model Receipt { receiptSignatureAlg String? receiptSignatureKid String? revoked Boolean @default(false) + + @@index([createdAt]) + @@index([revoked, createdAt]) + @@index([policyProfile, createdAt]) } model Property { diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index bbef345..9b417b2 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -59,6 +59,42 @@ export function loadRuntimeEnv(): void { runtimeEnvLoaded = true; } +/** + * Validates that all required environment variables are set. + * Call at startup before the server begins listening. + */ +export function validateRequiredEnv(env: NodeJS.ProcessEnv = process.env): void { + const required: string[] = []; + + // DATABASE_URL is always required (may be set via alias — check after resolveDatabaseUrl runs) + if (!env.DATABASE_URL && !env.SUPABASE_DB_URL && !env.SUPABASE_POOLER_URL && !env.SUPABASE_DIRECT_URL) { + required.push('DATABASE_URL'); + } + + // In production, receipt signing keys must be explicitly configured + if (env.NODE_ENV === 'production') { + if ( + !env.TRUSTSIGNAL_RECEIPT_SIGNING_PRIVATE_JWK && + !env.TRUSTSIGNAL_SIGNING_PRIVATE_JWK + ) { + required.push('TRUSTSIGNAL_RECEIPT_SIGNING_PRIVATE_JWK'); + } + if ( + !env.TRUSTSIGNAL_RECEIPT_SIGNING_KID && + !env.TRUSTSIGNAL_SIGNING_KEY_ID + ) { + required.push('TRUSTSIGNAL_RECEIPT_SIGNING_KID'); + } + } + + if (required.length > 0) { + throw new Error( + `[startup] Missing required environment variables: ${required.join(', ')}. ` + + 'Set them in your .env file or deployment environment before starting the server.' + ); + } +} + export function resolveDatabaseUrl(env: NodeJS.ProcessEnv = process.env): string | undefined { const databaseUrl = env.DATABASE_URL || diff --git a/apps/api/src/observability.test.ts b/apps/api/src/observability.test.ts index 879891f..b6a9230 100644 --- a/apps/api/src/observability.test.ts +++ b/apps/api/src/observability.test.ts @@ -93,8 +93,8 @@ describe.sequential('observability: correlation IDs and metrics endpoint', () => expect(response.statusCode).toBe(200); expect(response.headers['content-type']).toContain('text/plain'); const body = response.body; - expect(body).toContain('deedshield_http_requests_total'); - expect(body).toContain('deedshield_http_request_duration_seconds'); + expect(body).toContain('trustsignal_http_requests_total'); + expect(body).toContain('trustsignal_http_request_duration_seconds'); }); it('metrics endpoint exposes business-level verification lifecycle counters', async () => { @@ -105,10 +105,10 @@ describe.sequential('observability: correlation IDs and metrics endpoint', () => expect(response.statusCode).toBe(200); const body = response.body; - expect(body).toContain('deedshield_receipts_issued_total'); - expect(body).toContain('deedshield_receipt_verifications_total'); - expect(body).toContain('deedshield_revocations_total'); - expect(body).toContain('deedshield_verify_duration_seconds'); + expect(body).toContain('trustsignal_receipts_issued_total'); + expect(body).toContain('trustsignal_receipt_verifications_total'); + expect(body).toContain('trustsignal_revocations_total'); + expect(body).toContain('trustsignal_verify_duration_seconds'); }); it('metrics endpoint exposes default Node.js process metrics', async () => { @@ -120,7 +120,7 @@ describe.sequential('observability: correlation IDs and metrics endpoint', () => expect(response.statusCode).toBe(200); const body = response.body; // prom-client collectDefaultMetrics includes process_cpu_seconds_total - expect(body).toContain('deedshield_api_process_cpu_seconds_total'); + expect(body).toContain('trustsignal_api_process_cpu_seconds_total'); }); it('x-request-id is consistent in header and not a sensitive value', async () => { diff --git a/apps/api/src/registryLoader.test.ts b/apps/api/src/registryLoader.test.ts index 82f4d70..d46c219 100644 --- a/apps/api/src/registryLoader.test.ts +++ b/apps/api/src/registryLoader.test.ts @@ -1,6 +1,6 @@ import * as fsPromises from 'fs/promises'; -import { generateRegistryKeypair, signRegistry } from '@deed-shield/core'; +import { generateRegistryKeypair, signRegistry } from '@trustsignal/core'; import { afterEach, beforeEach, describe, expect, it, vi, type MockedFunction } from 'vitest'; import { loadRegistry } from './registryLoader.js'; diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index cc0764b..6d2c80b 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -44,7 +44,7 @@ import { mapInternalStatusToExternal, type ExternalReceiptStatus } from './recei import { anchorReceiptOnChain, buildAnchorSubject, type AnchorChain } from './anchor.js'; import { loadRegistry } from './registryLoader.js'; import { renderReceiptPdf } from './receiptPdf.js'; -import { loadRuntimeEnv, resolveDatabaseUrl } from './env.js'; +import { loadRuntimeEnv, resolveDatabaseUrl, validateRequiredEnv } from './env.js'; import { HttpAttomClient } from './services/attomClient.js'; import { CookCountyComplianceValidator } from './services/compliance.js'; import { @@ -81,6 +81,7 @@ import { loadRuntimeEnv(); resolveDatabaseUrl(); +validateRequiredEnv(); const prisma = new PrismaClient(); const REQUEST_START = Symbol('requestStartMs'); type RequestTimerState = { @@ -461,7 +462,7 @@ function receiptFromDb(record: ReceiptRecord) { decision: record.decision as 'ALLOW' | 'FLAG' | 'BLOCK', reasons: JSON.parse(record.reasons) as string[], riskScore: record.riskScore, - verifierId: 'deed-shield', + verifierId: 'trustsignal', ...(record.signingKeyId ? { signing_key_id: record.signingKeyId } : {}), receiptHash: record.receiptHash, fraudRisk: record.fraudRisk ? JSON.parse(record.fraudRisk) as DocumentRisk : undefined, @@ -925,7 +926,7 @@ async function issueReceiptRecord( } } - const receipt = buildReceipt(input, verification, 'deed-shield', { + const receipt = buildReceipt(input, verification, 'trustsignal', { signing_key_id: securityConfig.receiptSigning.current.kid, fraudRisk: options.fraudRisk, zkpAttestation @@ -1040,46 +1041,46 @@ export async function buildServer(options: BuildServerOptions = {}) { }); // Business-level verification lifecycle metrics const receiptsIssuedTotal = new Counter({ - name: 'deedshield_receipts_issued_total', + name: 'trustsignal_receipts_issued_total', help: 'Total signed receipts issued by decision outcome', labelNames: ['decision', 'policy_profile'] as const, registers: [metricsRegistry] }); const receiptVerificationsTotal = new Counter({ - name: 'deedshield_receipt_verifications_total', + name: 'trustsignal_receipt_verifications_total', help: 'Total post-issuance receipt verifications by outcome', labelNames: ['outcome'] as const, registers: [metricsRegistry] }); const revocationsTotal = new Counter({ - name: 'deedshield_revocations_total', + name: 'trustsignal_revocations_total', help: 'Total receipt revocations processed', labelNames: [] as const, registers: [metricsRegistry] }); const verifyDurationSeconds = new Histogram({ - name: 'deedshield_verify_duration_seconds', + name: 'trustsignal_verify_duration_seconds', help: 'End-to-end duration of the verification and receipt issuance flow', labelNames: ['decision'] as const, buckets: [0.1, 0.25, 0.5, 1, 2, 5, 10], registers: [metricsRegistry] }); const receiptLookupDurationSeconds = new Histogram({ - name: 'deedshield_receipt_lookup_duration_seconds', + name: 'trustsignal_receipt_lookup_duration_seconds', help: 'Duration of receipt retrieval from database (GET /receipt/:id)', labelNames: [] as const, buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5], registers: [metricsRegistry] }); const anchorDurationSeconds = new Histogram({ - name: 'deedshield_anchor_duration_seconds', + name: 'trustsignal_anchor_duration_seconds', help: 'Duration of receipt anchoring operation by chain', labelNames: ['chain'] as const, buckets: [0.1, 0.5, 1, 2, 5, 10, 30], registers: [metricsRegistry] }); const httpErrorsTotal = new Counter({ - name: 'deedshield_http_errors_total', + name: 'trustsignal_http_errors_total', help: 'Total HTTP error responses (4xx/5xx) by route and status code', labelNames: ['method', 'route', 'status_code'] as const, registers: [metricsRegistry] @@ -1193,7 +1194,7 @@ export async function buildServer(options: BuildServerOptions = {}) { const forwardedProto = normalizeForwardedProto(request.headers['x-forwarded-proto']); return { status: 'ok', - service: 'deed-shield-api', + service: 'trustsignal-api', version: process.env.TRUSTSIGNAL_VERSION || 'dev', environment: process.env.NODE_ENV || 'development', uptimeSeconds: Math.floor(process.uptime()), @@ -1212,7 +1213,9 @@ export async function buildServer(options: BuildServerOptions = {}) { } }; }); - app.get('/api/v1/metrics', async (_request, reply) => { + app.get('/api/v1/metrics', { + preHandler: [requireScope('read')] + }, async (_request, reply) => { reply.header('Content-Type', metricsRegistry.contentType); return reply.send(await metricsRegistry.metrics()); }); @@ -1957,6 +1960,7 @@ export async function buildServer(options: BuildServerOptions = {}) { if (record.revoked) { return reply.send({ + status: 'REVOKED', receiptStatus: 'revoked' satisfies ExternalReceiptStatus, result: 'ALREADY_REVOKED' }); @@ -1979,6 +1983,7 @@ export async function buildServer(options: BuildServerOptions = {}) { ); return reply.send({ + status: 'REVOKED', receiptStatus: 'revoked' satisfies ExternalReceiptStatus, result: 'REVOKED', issuerId: revocationVerification.issuerId diff --git a/apps/web/package.json b/apps/web/package.json index b90bb5c..57741ae 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,5 +1,5 @@ { - "name": "@deed-shield/web", + "name": "@trustsignal/web", "version": "0.2.0", "private": true, "type": "module", @@ -12,7 +12,7 @@ }, "dependencies": { "fastify": "5.8.3", - "next": "^16.2.0", + "next": "^16.2.3", "react": "18.3.1", "react-dom": "18.3.1", "react-dropzone": "^14.3.8", diff --git a/bench/run-bench.ts b/bench/run-bench.ts index 767d7f9..cc02c10 100644 --- a/bench/run-bench.ts +++ b/bench/run-bench.ts @@ -422,7 +422,7 @@ async function scenarioClean( checks: receipt.checks }; const started = performance.now(); - const rebuiltReceipt = buildReceipt(bundle, verificationLike, 'deed-shield', { + const rebuiltReceipt = buildReceipt(bundle, verificationLike, 'trustsignal', { fraudRisk: receipt.fraudRisk, zkpAttestation: receipt.zkpAttestation }); diff --git a/docs/final/10_INCIDENT_ESCALATION_AND_SLO_BASELINE.md b/docs/final/10_INCIDENT_ESCALATION_AND_SLO_BASELINE.md index b365616..4276601 100644 --- a/docs/final/10_INCIDENT_ESCALATION_AND_SLO_BASELINE.md +++ b/docs/final/10_INCIDENT_ESCALATION_AND_SLO_BASELINE.md @@ -42,17 +42,17 @@ Use `/api/v1/metrics` Prometheus data: - Severity: `SEV-2` (escalate to `SEV-1` if >15 minutes sustained). 2. Error rate alert: -- Signal: `5xx / total requests` using `deedshield_http_requests_total`. +- Signal: `5xx / total requests` using `trustsignal_http_requests_total`. - Warning: `> 2%` for 10 minutes. - Critical: `> 5%` for 5 minutes. 3. Latency alert: -- Signal: p95 from `deedshield_http_request_duration_seconds`. +- Signal: p95 from `trustsignal_http_request_duration_seconds`. - Warning: `> 1.0s` for 10 minutes. - Critical: `> 2.5s` for 5 minutes. 4. Traffic drop alert: -- Signal: request rate from `deedshield_http_requests_total`. +- Signal: request rate from `trustsignal_http_requests_total`. - Warning: request volume drops >70% from 24h baseline for 15 minutes (business hours). ## Required Artifacts for Gate Evidence diff --git a/docs/ops/monitoring/README.md b/docs/ops/monitoring/README.md index 63652be..a58c5ce 100644 --- a/docs/ops/monitoring/README.md +++ b/docs/ops/monitoring/README.md @@ -75,13 +75,13 @@ Store screenshots in staging evidence (for example under `docs/evidence/staging/ ## Notes - The alert rules use metrics emitted by `apps/api/src/server.ts`: - **HTTP infrastructure metrics:** - - `deedshield_http_requests_total` (labels: `method`, `route`, `status_code`) - - `deedshield_http_request_duration_seconds` (labels: `method`, `route`, `status_code`) + - `trustsignal_http_requests_total` (labels: `method`, `route`, `status_code`) + - `trustsignal_http_request_duration_seconds` (labels: `method`, `route`, `status_code`) - **Verification lifecycle business metrics:** - - `deedshield_receipts_issued_total` (labels: `decision`, `policy_profile`) — incremented per signed receipt issued - - `deedshield_receipt_verifications_total` (labels: `outcome`: `verified` | `not_verified`) — incremented per post-issuance receipt verification - - `deedshield_revocations_total` — incremented per receipt revocation - - `deedshield_verify_duration_seconds` (labels: `decision`) — histogram of end-to-end verify+receipt-issue duration + - `trustsignal_receipts_issued_total` (labels: `decision`, `policy_profile`) — incremented per signed receipt issued + - `trustsignal_receipt_verifications_total` (labels: `outcome`: `verified` | `not_verified`) — incremented per post-issuance receipt verification + - `trustsignal_revocations_total` — incremented per receipt revocation + - `trustsignal_verify_duration_seconds` (labels: `decision`) — histogram of end-to-end verify+receipt-issue duration - Core latency scope follows baseline routes: - `/api/v1/verify` - `/api/v1/receipt/:receiptId` diff --git a/docs/ops/monitoring/grafana-dashboard-deedshield-api.json b/docs/ops/monitoring/grafana-dashboard-deedshield-api.json index 95d0869..6327c9b 100644 --- a/docs/ops/monitoring/grafana-dashboard-deedshield-api.json +++ b/docs/ops/monitoring/grafana-dashboard-deedshield-api.json @@ -86,7 +86,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum(rate(deedshield_http_requests_total{job=~\"$job\",route=\"/api/v1/health\",status_code=~\"2..\"}[5m])) / clamp_min(sum(rate(deedshield_http_requests_total{job=~\"$job\",route=\"/api/v1/health\"}[5m])), 0.001)", + "expr": "sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=\"/api/v1/health\",status_code=~\"2..\"}[5m])) / clamp_min(sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=\"/api/v1/health\"}[5m])), 0.001)", "legendFormat": "health success ratio (5m)", "range": true, "refId": "A" @@ -145,7 +145,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum(rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m]))", + "expr": "sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m]))", "legendFormat": "requests/sec", "range": true, "refId": "A" @@ -212,7 +212,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by (le) (rate(deedshield_http_request_duration_seconds_bucket{job=~\"$job\",route=~\"^/api/v1/(verify|receipt/:receiptId|receipt/:receiptId/verify)$\"}[5m])))", + "expr": "histogram_quantile(0.95, sum by (le) (rate(trustsignal_http_request_duration_seconds_bucket{job=~\"$job\",route=~\"^/api/v1/(verify|receipt/:receiptId|receipt/:receiptId/verify)$\"}[5m])))", "legendFormat": "core p95", "range": true, "refId": "A" @@ -276,7 +276,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum(rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\",status_code=~\"5..\"}[5m])) / clamp_min(sum(rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m])), 0.001)", + "expr": "sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\",status_code=~\"5..\"}[5m])) / clamp_min(sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m])), 0.001)", "legendFormat": "5xx ratio", "range": true, "refId": "A" @@ -340,7 +340,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by (le, route) (rate(deedshield_http_request_duration_seconds_bucket{job=~\"$job\",route=~\"^/api/v1/(verify|receipt/:receiptId|receipt/:receiptId/verify)$\"}[5m])))", + "expr": "histogram_quantile(0.95, sum by (le, route) (rate(trustsignal_http_request_duration_seconds_bucket{job=~\"$job\",route=~\"^/api/v1/(verify|receipt/:receiptId|receipt/:receiptId/verify)$\"}[5m])))", "legendFormat": "{{route}}", "range": true, "refId": "A" @@ -396,7 +396,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum by (route) (rate(deedshield_http_requests_total{job=~\"$job\",route=~\"^/api/v1/(verify|receipt/:receiptId|receipt/:receiptId/verify)$\"}[5m]))", + "expr": "sum by (route) (rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"^/api/v1/(verify|receipt/:receiptId|receipt/:receiptId/verify)$\"}[5m]))", "legendFormat": "{{route}}", "range": true, "refId": "A" @@ -452,7 +452,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum by (status_code) (rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m]))", + "expr": "sum by (status_code) (rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m]))", "legendFormat": "{{status_code}}", "range": true, "refId": "A" @@ -516,7 +516,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum(rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m])) / clamp_min(sum(rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[24h])), 0.001)", + "expr": "sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m])) / clamp_min(sum(rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[24h])), 0.001)", "legendFormat": "current vs 24h baseline", "range": true, "refId": "A" @@ -580,7 +580,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum by (route) (rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\",status_code=~\"5..\"}[5m])) / clamp_min(sum by (route) (rate(deedshield_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m])), 0.001)", + "expr": "sum by (route) (rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\",status_code=~\"5..\"}[5m])) / clamp_min(sum by (route) (rate(trustsignal_http_requests_total{job=~\"$job\",route=~\"/api/v1/.*\",route!=\"/api/v1/metrics\"}[5m])), 0.001)", "format": "table", "instant": true, "legendFormat": "{{route}}", @@ -637,7 +637,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum by (decision) (rate(deedshield_receipts_issued_total{job=~\"$job\"}[5m]))", + "expr": "sum by (decision) (rate(trustsignal_receipts_issued_total{job=~\"$job\"}[5m]))", "legendFormat": "{{ decision }}", "range": true, "refId": "A" @@ -701,7 +701,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "histogram_quantile(0.95, sum by (le) (rate(deedshield_verify_duration_seconds_bucket{job=~\"$job\"}[5m])))", + "expr": "histogram_quantile(0.95, sum by (le) (rate(trustsignal_verify_duration_seconds_bucket{job=~\"$job\"}[5m])))", "legendFormat": "p95 duration", "range": true, "refId": "A" @@ -765,7 +765,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum(increase(deedshield_revocations_total{job=~\"$job\"}[5m]))", + "expr": "sum(increase(trustsignal_revocations_total{job=~\"$job\"}[5m]))", "legendFormat": "revocations", "range": true, "refId": "A" @@ -829,7 +829,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum(rate(deedshield_receipt_verifications_total{job=~\"$job\",outcome=\"verified\"}[5m])) / clamp_min(sum(rate(deedshield_receipt_verifications_total{job=~\"$job\"}[5m])), 0.001)", + "expr": "sum(rate(trustsignal_receipt_verifications_total{job=~\"$job\",outcome=\"verified\"}[5m])) / clamp_min(sum(rate(trustsignal_receipt_verifications_total{job=~\"$job\"}[5m])), 0.001)", "legendFormat": "verified ratio", "range": true, "refId": "A" @@ -878,7 +878,7 @@ "type": "prometheus", "uid": "$datasource" }, - "definition": "label_values(deedshield_http_requests_total, job)", + "definition": "label_values(trustsignal_http_requests_total, job)", "hide": 0, "includeAll": true, "label": "Prometheus job", @@ -886,7 +886,7 @@ "name": "job", "options": [], "query": { - "query": "label_values(deedshield_http_requests_total, job)", + "query": "label_values(trustsignal_http_requests_total, job)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, diff --git a/package-lock.json b/package-lock.json index c16bda5..e24bd32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "deed-shield", + "name": "trustsignal", "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "deed-shield", + "name": "trustsignal", "version": "0.2.0", "hasInstallScript": true, "workspaces": [ @@ -13,7 +13,7 @@ "packages/*" ], "dependencies": { - "axios": "^1.13.6", + "axios": "^1.15.0", "better-sqlite3": "^12.8.0", "busboy": "^1.6.0", "chokidar": "^4.0.3", @@ -45,15 +45,15 @@ } }, "apps/api": { - "name": "@deed-shield/api", + "name": "@trustsignal/api", "version": "0.2.0", "hasInstallScript": true, "dependencies": { - "@deed-shield/core": "file:../../packages/core", "@fastify/cors": "^11.2.0", "@fastify/rate-limit": "^10.3.0", "@prisma/client": "^5.17.0", "@solana/web3.js": "^1.98.4", + "@trustsignal/core": "file:../../packages/core", "ethers": "^6.12.0", "fastify": "^5.8.3", "openai": "^6.17.0", @@ -121,11 +121,11 @@ "license": "UNLICENSED" }, "apps/web": { - "name": "@deed-shield/web", + "name": "@trustsignal/web", "version": "0.2.0", "dependencies": { "fastify": "5.8.3", - "next": "^16.2.0", + "next": "^16.2.3", "react": "18.3.1", "react-dom": "18.3.1", "react-dropzone": "^14.3.8", @@ -310,9 +310,8 @@ } }, "apps/web/node_modules/next": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.0.tgz", - "integrity": "sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==", + "version": "16.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", "license": "MIT", "dependencies": { "@next/env": "16.2.0", @@ -918,22 +917,6 @@ "node": ">=18" } }, - "node_modules/@deed-shield/api": { - "resolved": "apps/api", - "link": true - }, - "node_modules/@deed-shield/contracts": { - "resolved": "packages/contracts", - "link": true - }, - "node_modules/@deed-shield/core": { - "resolved": "packages/core", - "link": true - }, - "node_modules/@deed-shield/web": { - "resolved": "apps/web", - "link": true - }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -3435,6 +3418,22 @@ } } }, + "node_modules/@trustsignal/api": { + "resolved": "apps/api", + "link": true + }, + "node_modules/@trustsignal/contracts": { + "resolved": "packages/contracts", + "link": true + }, + "node_modules/@trustsignal/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@trustsignal/web": { + "resolved": "apps/web", + "link": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -4445,14 +4444,14 @@ } }, "node_modules/axios": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { @@ -9305,10 +9304,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pump": { "version": "3.0.3", @@ -11832,7 +11834,7 @@ } }, "packages/contracts": { - "name": "@deed-shield/contracts", + "name": "@trustsignal/contracts", "version": "0.2.0", "dependencies": { "fastify": "5.8.3" @@ -12140,7 +12142,7 @@ } }, "packages/core": { - "name": "@deed-shield/core", + "name": "@trustsignal/core", "version": "0.2.0", "dependencies": { "ethers": "^6.12.0", diff --git a/package.json b/package.json index 3b7dfb2..84357fe 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "deed-shield", + "name": "trustsignal", "private": true, "version": "0.2.0", "type": "commonjs", @@ -11,7 +11,7 @@ "packages/*" ], "dependencies": { - "axios": "^1.13.6", + "axios": "^1.15.0", "better-sqlite3": "^12.8.0", "busboy": "^1.6.0", "chokidar": "^4.0.3", diff --git a/packages/README.md b/packages/README.md index 48cae61..be41e50 100644 --- a/packages/README.md +++ b/packages/README.md @@ -1,10 +1,7 @@ # TrustSignal Packages -All packages under the `@deed-shield/*` scope are **legacy identifiers** -maintained for backward compatibility with early integrations. +All packages are published under the `@trustsignal/*` scope. -**Current naming:** `@deed-shield/core`, `@deed-shield/verifier` -**Future naming:** `@trustsignal/core`, `@trustsignal/verifier` -(Planned for v1.0 with aliasing and deprecation warnings) +**Current naming:** `@trustsignal/core`, `@trustsignal/contracts` -For new projects, treat `@deed-shield/*` as TrustSignal components. +The legacy `@deed-shield/*` scope has been retired. Update any imports to use `@trustsignal/*`. diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 27f1c1f..d45b1c3 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,5 +1,5 @@ { - "name": "@deed-shield/contracts", + "name": "@trustsignal/contracts", "version": "0.2.0", "private": true, "type": "module", diff --git a/packages/core/package.json b/packages/core/package.json index d5f09a4..e285f43 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@deed-shield/core", + "name": "@trustsignal/core", "version": "0.2.0", "private": true, "type": "commonjs", diff --git a/packages/core/src/receipt.ts b/packages/core/src/receipt.ts index 2426faa..bd054f0 100644 --- a/packages/core/src/receipt.ts +++ b/packages/core/src/receipt.ts @@ -33,7 +33,7 @@ export function toUnsignedReceiptPayload(receipt: Receipt): UnsignedReceiptPaylo export function buildReceipt( input: BundleInput, verification: VerificationResult, - verifierId = 'deed-shield', + verifierId = 'trustsignal', extensions: { fraudRisk?: Receipt['fraudRisk']; zkpAttestation?: Receipt['zkpAttestation']; diff --git a/packages/core/src/receiptSigner.test.ts b/packages/core/src/receiptSigner.test.ts index f75eabf..8f6490c 100644 --- a/packages/core/src/receiptSigner.test.ts +++ b/packages/core/src/receiptSigner.test.ts @@ -34,7 +34,7 @@ describe('receipt signing', () => { timestamp: new Date().toISOString() }; const verification = await verifyBundle(bundle, registry); - const receipt = buildReceipt(bundle, verification, 'deed-shield'); + const receipt = buildReceipt(bundle, verification, 'trustsignal'); const unsignedPayload = toUnsignedReceiptPayload(receipt); const { privateJwk } = await generateRegistryKeypair(); @@ -74,7 +74,7 @@ describe('receipt signing', () => { timestamp: new Date().toISOString() }; const verification = await verifyBundle(bundle, registry); - const receipt = buildReceipt(bundle, verification, 'deed-shield', { signing_key_id: signingKeyId }); + const receipt = buildReceipt(bundle, verification, 'trustsignal', { signing_key_id: signingKeyId }); const unsignedPayload = toUnsignedReceiptPayload(receipt); expect(unsignedPayload.signing_key_id).toBe(signingKeyId); @@ -119,7 +119,7 @@ describe('receipt signing', () => { timestamp: new Date().toISOString() }; const verification = await verifyBundle(bundle, registry); - const receipt = buildReceipt(bundle, verification, 'deed-shield', { signing_key_id: signingKeyId }); + const receipt = buildReceipt(bundle, verification, 'trustsignal', { signing_key_id: signingKeyId }); const unsignedPayload = toUnsignedReceiptPayload(receipt); const keypair = await generateRegistryKeypair(); const receiptSignature = await signReceiptPayload(unsignedPayload, { @@ -157,7 +157,7 @@ describe('receipt signing', () => { timestamp: new Date().toISOString() }; const verification = await verifyBundle(bundle, registry); - const receipt = buildReceipt(bundle, verification, 'deed-shield'); + const receipt = buildReceipt(bundle, verification, 'trustsignal'); const unsignedPayload = toUnsignedReceiptPayload(receipt); const { privateJwk, publicJwk } = await generateRegistryKeypair(); const receiptSignature = await signReceiptPayload(unsignedPayload, { @@ -207,7 +207,7 @@ describe('receipt signing', () => { timestamp: new Date().toISOString() }; const verification = await verifyBundle(bundle, registry); - const receipt = buildReceipt(bundle, verification, 'deed-shield'); + const receipt = buildReceipt(bundle, verification, 'trustsignal'); const unsignedPayload = toUnsignedReceiptPayload(receipt); const malformed = await verifyReceiptSignature( diff --git a/supabase/migrations/20260410000000_rls_policies.sql b/supabase/migrations/20260410000000_rls_policies.sql new file mode 100644 index 0000000..79a28b8 --- /dev/null +++ b/supabase/migrations/20260410000000_rls_policies.sql @@ -0,0 +1,55 @@ +-- TrustSignal RLS policies +-- Enforces tenant-scoped row-level security on all customer-facing tables. +-- All tables holding tenant data must have RLS enabled and a SELECT/INSERT/UPDATE/DELETE +-- policy that restricts to the requesting API key's owner (user_id). +-- +-- This migration is idempotent — safe to re-run. + +-- ─── api_keys ───────────────────────────────────────────────────────────────── +-- Tenants may only see and rotate their own keys. + +ALTER TABLE public.api_keys ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "api_keys_owner_select" ON public.api_keys; +CREATE POLICY "api_keys_owner_select" + ON public.api_keys FOR SELECT + USING (user_id = auth.uid()); + +DROP POLICY IF EXISTS "api_keys_owner_insert" ON public.api_keys; +CREATE POLICY "api_keys_owner_insert" + ON public.api_keys FOR INSERT + WITH CHECK (user_id = auth.uid()); + +DROP POLICY IF EXISTS "api_keys_owner_update" ON public.api_keys; +CREATE POLICY "api_keys_owner_update" + ON public.api_keys FOR UPDATE + USING (user_id = auth.uid()) + WITH CHECK (user_id = auth.uid()); + +DROP POLICY IF EXISTS "api_keys_owner_delete" ON public.api_keys; +CREATE POLICY "api_keys_owner_delete" + ON public.api_keys FOR DELETE + USING (user_id = auth.uid()); + +-- ─── tenants / customers ────────────────────────────────────────────────────── +-- Each row is isolated to the owning user. Adjust table name if different. + +ALTER TABLE IF EXISTS public.tenants ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "tenants_owner_select" ON public.tenants; +CREATE POLICY "tenants_owner_select" + ON public.tenants FOR SELECT + USING (user_id = auth.uid()); + +-- ─── usage_events ───────────────────────────────────────────────────────────── +-- Tenants may only query their own usage records. + +ALTER TABLE IF EXISTS public.usage_events ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "usage_events_owner_select" ON public.usage_events; +CREATE POLICY "usage_events_owner_select" + ON public.usage_events FOR SELECT + USING (user_id = auth.uid()); + +-- Service role bypasses RLS (used by the API server via service key). +-- Nothing to configure here — service_role bypasses RLS by default in Supabase.