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

@@ -30,6 +30,7 @@ import { createDeprovisioner } from './deprovision/deprovision.js'
import { createAuthorizer, type CapabilityVerifier } from './api/authz.js'
import { buildRouter } from './api/provision.js'
import { buildCaSigner, inProcessCaSigner, type KmsResolver } from './boot/ca-wiring.js'
import { configureCapabilityVerifyKey } from './boot/verifier.js'
import type { RevocationBus } from 'relay-contracts'
export interface ControlPlaneOverrides {
@@ -40,9 +41,13 @@ export interface ControlPlaneOverrides {
readonly caChainDer?: readonly Uint8Array[]
}
/** Default fail-closed verifier — refuses everything until P5 is wired (INV6). */
/**
* Default fail-closed verifier — refuses everything until a real (P5) verifier is injected (INV6).
* Async-shaped to match `CapabilityVerifier`: an async body that throws rejects the promise, so the
* authorizer's `await` surfaces it as a 401.
*/
const refuseAllVerifier: CapabilityVerifier = {
verify() {
async verify(): Promise<never> {
throw new Error('capability verification not configured (P5 integration point)')
},
}
@@ -92,8 +97,16 @@ export async function buildControlPlane(
const deprovisioner = createDeprovisioner({ hosts, routing })
void bus // reserved for the node-coordinator / revoker wiring (drain + revoke publishers)
// A real (injected) verifier is P5's async `verifyCapabilityToken`, which reads its Ed25519 key
// from relay-auth's startup registry — so load that key from env at boot. The fail-closed default
// never reads a key, so leave relay-auth's registry untouched when nothing is injected (INV6).
const verifier = overrides.verifier ?? refuseAllVerifier
if (overrides.verifier !== undefined) {
await configureCapabilityVerifyKey(env.capabilitySignPubkey)
}
const authorizer = createAuthorizer({
verifier: overrides.verifier ?? refuseAllVerifier,
verifier,
expectedAud: env.baseDomain,
})