feat(relay): Phase1 foundation — P3 Postgres store + async capability verifier + compose

RELAY-PHASE1 Wave A (2/3) + E1 infra:
- A1: createPgStores() Postgres adapter for all 9 P3 store ports + runMigrations();
  writable-CTE atomic INV8 status swaps; 0002_routes.sql. 23/23 pg tests, 96.72% cov.
- A3: real capability verifier delegating to relay-auth verifyCapabilityToken; sync->async
  seam across authz/provision/main. Full CP suite 15 files/101 pass, tsc clean.
- E1: deploy/docker-compose.yml (Postgres16+Redis7, loopback-only) + .env.example.
- docs: PLAN_RELAY_PHASE1.md file-level execution spec; PROGRESS_LOG RELAY-PHASE1 section.
This commit is contained in:
Yaojia Wang
2026-07-06 14:51:37 +02:00
parent 242a4e0dc1
commit 95b9cccf07
17 changed files with 1488 additions and 16 deletions

View File

@@ -0,0 +1,38 @@
/**
* A3 — REAL capability verifier, backed by relay-auth's `verifyCapabilityToken` (P5). Replaces the
* fail-closed `refuseAllVerifier` stub. The frozen §4.3 verify signature is ASYNC (Ed25519 verify
* over WebCrypto) and reads its verifying key from relay-auth's startup registry (config/keys.ts),
* never as a parameter — so we (a) adapt it to the async `CapabilityVerifier` seam and (b) load the
* key at boot from the CP env's already-validated pubkey.
*
* KEY BRIDGE (INV9): the CP env var is `CAPABILITY_SIGN_PUBKEY_B64` (base64), parsed+validated in
* `env.ts` to `env.capabilitySignPubkey` (32 raw Ed25519 bytes). relay-auth's own env loader expects
* a DIFFERENT var name (`RELAY_AUTH_VERIFY_PUBKEY`, base64url), so we do NOT use it — we import the
* 32 raw bytes to a non-exportable verify-only CryptoKey via WebCrypto and call relay-auth's
* `configureVerifyKey(CryptoKey)` directly.
*/
import { verifyCapabilityToken, configureVerifyKey } from 'relay-auth'
import type { CapabilityToken } from 'relay-contracts'
import type { CapabilityVerifier } from '../api/authz.js'
/** The real verifier: delegates verbatim to P5's frozen §4.3 `verifyCapabilityToken`. */
export function createCapabilityVerifier(): CapabilityVerifier {
return {
verify(raw: string, expectedAud: string, now: number): Promise<CapabilityToken> {
return verifyCapabilityToken(raw, expectedAud, now)
},
}
}
/**
* Load the §4.3 verifying key into relay-auth's startup registry from the CP env's raw 32-byte
* Ed25519 public key (`env.capabilitySignPubkey`). Non-exportable, `verify`-only. Must run before
* the real verifier serves any request, else `verifyCapabilityToken` throws `KeyConfigError`.
*/
export async function configureCapabilityVerifyKey(pubkeyRaw: Uint8Array): Promise<void> {
// Copy into a fresh ArrayBuffer-backed view (WebCrypto's BufferSource typing / no shared pool).
const bytes = new Uint8Array(pubkeyRaw.length)
bytes.set(pubkeyRaw)
const key = await globalThis.crypto.subtle.importKey('raw', bytes, { name: 'Ed25519' }, false, ['verify'])
configureVerifyKey(key)
}