feat(omnl): settlement terminal, compliance gates, and HYBX integration foundation
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m12s
CI/CD Pipeline / Security Scanning (push) Successful in 2m21s
CI/CD Pipeline / Lint and Format (push) Failing after 36s
CI/CD Pipeline / Terraform Validation (push) Failing after 22s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 25s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 23s
Validation / validate-genesis (push) Successful in 26s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 9s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m15s
Validation / validate-documentation (push) Failing after 15s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s

Add operator settlement terminal UI/API, swift-listener service, compliance
idempotency gates, GRU reserve dashboards, and @dbis/integration-foundation
for typed HYBX/ISO adapter contracts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-07 21:51:32 -07:00
parent b6ddd236e2
commit d717b504a6
182 changed files with 10852 additions and 40 deletions

View File

@@ -1,11 +1,17 @@
import type { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
import { createCorrelationContext, CORRELATION_ID_HEADER, REQUEST_ID_HEADER } from '@dbis/integration-foundation';
import { appendOmnlAudit } from '../../services/omnl-audit-log';
/** Attach trace id and log every /omnl request (response status on finish). */
/** Attach trace/correlation ids and log every /omnl request (response status on finish). */
export function omnlAuditMiddleware(req: Request, res: Response, next: NextFunction): void {
const traceId = (req.headers['x-omnl-trace-id'] as string) || randomUUID();
const ctx = createCorrelationContext({
correlationId: String(req.headers[CORRELATION_ID_HEADER] ?? req.headers['x-omnl-trace-id'] ?? ''),
requestId: String(req.headers[REQUEST_ID_HEADER] ?? ''),
});
const traceId = ctx.correlationId;
res.setHeader('X-OMNL-Trace-Id', traceId);
res.setHeader(CORRELATION_ID_HEADER, ctx.correlationId);
res.setHeader(REQUEST_ID_HEADER, ctx.requestId);
const started = Date.now();
res.on('finish', () => {
appendOmnlAudit({

View File

@@ -0,0 +1,79 @@
import type { Request, Response, NextFunction } from 'express';
import { MockComplianceDecisionEngine } from '@dbis/integration-foundation';
const engine = new MockComplianceDecisionEngine();
const FINANCIAL_POST_PATHS = [
'/omnl/iso20022/messages',
];
/**
* Optional compliance decision gate for financial POST routes.
* Enable with OMNL_COMPLIANCE_GATE=1 (uses mock engine until provider integration).
*/
export async function omnlComplianceGateMiddleware(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
if (process.env.OMNL_COMPLIANCE_GATE !== '1') {
next();
return;
}
if (req.method !== 'POST') {
next();
return;
}
const path = req.path.replace(/^\/api\/v1/, '');
if (!FINANCIAL_POST_PATHS.some((p) => path.endsWith(p))) {
next();
return;
}
const body = req.body as {
messageType?: string;
payload?: string;
entityId?: string;
tenantId?: string;
amount?: string;
currency?: string;
riskHints?: string[];
};
const result = await engine.evaluateTransaction({
entityId: body.entityId ?? 'omnl-default',
tenantId: body.tenantId ?? 'omnl',
amount: body.amount ?? '0',
currency: body.currency ?? 'USD',
transactionType: body.messageType ?? 'iso20022.store',
riskHints: body.riskHints,
});
res.setHeader('X-OMNL-Compliance-Decision', result.decision);
res.setHeader('X-OMNL-Audit-Event-Id', result.audit_event_id);
if (result.decision === 'block' || result.decision === 'reject') {
res.status(403).json({
error: 'compliance_blocked',
decision: result.decision,
reason_codes: result.reason_codes,
audit_event_id: result.audit_event_id,
manual_review_required: result.manual_review_required,
});
return;
}
if (result.decision === 'hold' || result.decision === 'review') {
res.status(202).json({
status: 'pending_review',
decision: result.decision,
reason_codes: result.reason_codes,
audit_event_id: result.audit_event_id,
manual_review_required: true,
});
return;
}
(req as Request & { complianceAuditId?: string }).complianceAuditId = result.audit_event_id;
next();
}

View File

@@ -0,0 +1,91 @@
import type { Request, Response } from 'express';
import { omnlComplianceGateMiddleware } from './omnl-compliance-gate';
import { omnlIdempotencyMiddleware } from './omnl-idempotency';
function mockRes(): Response & { statusCode?: number; body?: unknown; headers: Record<string, string> } {
const headers: Record<string, string> = {};
const res = {
headers,
statusCode: 200,
body: undefined as unknown,
setHeader(name: string, value: string) {
headers[name.toLowerCase()] = value;
},
status(code: number) {
this.statusCode = code;
return this;
},
json(payload: unknown) {
this.body = payload;
return this;
},
};
return res as Response & typeof res;
}
function mockReq(overrides: Partial<Request> & { path?: string; method?: string } = {}): Request {
return {
method: 'POST',
path: '/api/v1/omnl/iso20022/messages',
headers: {},
body: { messageType: 'pacs.008', payload: '<xml/>' },
...overrides,
} as Request;
}
describe('omnlComplianceGateMiddleware', () => {
const oldGate = process.env.OMNL_COMPLIANCE_GATE;
afterEach(() => {
if (oldGate === undefined) delete process.env.OMNL_COMPLIANCE_GATE;
else process.env.OMNL_COMPLIANCE_GATE = oldGate;
});
it('passes through when OMNL_COMPLIANCE_GATE is unset', async () => {
delete process.env.OMNL_COMPLIANCE_GATE;
const next = jest.fn();
await omnlComplianceGateMiddleware(mockReq(), mockRes(), next);
expect(next).toHaveBeenCalled();
});
it('sets compliance headers and passes when gate enabled', async () => {
process.env.OMNL_COMPLIANCE_GATE = '1';
const next = jest.fn();
const res = mockRes();
await omnlComplianceGateMiddleware(mockReq(), res, next);
expect(res.headers['x-omnl-compliance-decision']).toBeDefined();
expect(res.headers['x-omnl-audit-event-id']).toBeDefined();
expect(next).toHaveBeenCalled();
});
it('skips non-POST methods', async () => {
process.env.OMNL_COMPLIANCE_GATE = '1';
const next = jest.fn();
await omnlComplianceGateMiddleware(mockReq({ method: 'GET' }), mockRes(), next);
expect(next).toHaveBeenCalled();
});
});
describe('omnlIdempotencyMiddleware', () => {
it('passes when X-Idempotency-Key is absent', () => {
const next = jest.fn();
omnlIdempotencyMiddleware(mockReq(), mockRes(), next);
expect(next).toHaveBeenCalled();
});
it('returns 409 on duplicate idempotency key', () => {
const req = mockReq({
headers: { 'x-idempotency-key': 'idem-test-duplicate-key' },
});
const next = jest.fn();
omnlIdempotencyMiddleware(req, mockRes(), next);
expect(next).toHaveBeenCalledTimes(1);
const res = mockRes();
const next2 = jest.fn();
omnlIdempotencyMiddleware(req, res, next2);
expect(res.statusCode).toBe(409);
expect(next2).not.toHaveBeenCalled();
expect(res.body).toMatchObject({ error: 'duplicate_idempotency_key' });
});
});

View File

@@ -0,0 +1,41 @@
import type { Request, Response, NextFunction } from 'express';
import { deriveIdempotencyKey, IdempotencyStore } from '@dbis/integration-foundation';
const store = new IdempotencyStore();
const IDEMPOTENT_PATHS = ['/omnl/iso20022/messages'];
/**
* Dedup financial POSTs when X-Idempotency-Key header is present.
*/
export function omnlIdempotencyMiddleware(req: Request, res: Response, next: NextFunction): void {
if (req.method !== 'POST') {
next();
return;
}
const path = req.path.replace(/^\/api\/v1/, '');
if (!IDEMPOTENT_PATHS.some((p) => path.endsWith(p))) {
next();
return;
}
const headerKey = String(req.headers['x-idempotency-key'] ?? '').trim();
if (!headerKey) {
next();
return;
}
const key = deriveIdempotencyKey({
tenantId: String(req.headers['x-tenant-id'] ?? 'omnl'),
entityId: String(req.headers['x-entity-id'] ?? 'default'),
operation: path,
resourceId: headerKey,
});
if (!store.record(key)) {
res.status(409).json({ error: 'duplicate_idempotency_key', idempotencyKey: headerKey });
return;
}
next();
}

View File

@@ -2,6 +2,8 @@ import { Router, Request, Response } from 'express';
import { omnlRateLimiter } from '../middleware/rate-limit';
import { omnlSensitiveRouteGuard, omnlRequireApiKeyInProduction } from '../middleware/omnl-guards';
import { omnlAuditMiddleware } from '../middleware/omnl-audit-middleware';
import { omnlComplianceGateMiddleware } from '../middleware/omnl-compliance-gate';
import { omnlIdempotencyMiddleware } from '../middleware/omnl-idempotency';
import { runTripleStateReconcile } from '../../services/omnl-triple-reconcile';
import {
buildIfrs7Disclosure,
@@ -31,6 +33,8 @@ const router = Router();
router.use(omnlRateLimiter);
router.use(omnlAuditMiddleware);
router.use(omnlRequireApiKeyInProduction);
router.use(omnlIdempotencyMiddleware);
router.use(omnlComplianceGateMiddleware);
router.get('/omnl/reconcile/triple-state', omnlSensitiveRouteGuard, async (req: Request, res: Response) => {
try {

View File

@@ -0,0 +1,278 @@
import { Router, Request, Response } from 'express';
import { omnlRateLimiter } from '../middleware/rate-limit';
import { omnlSensitiveRouteGuard, omnlRequireApiKeyInProduction } from '../middleware/omnl-guards';
import { omnlAuditMiddleware } from '../middleware/omnl-audit-middleware';
import {
buildSettlementTerminalContext,
buildFullTerminalPackage,
getTerminalArtifact,
renderTerminalScreen,
type TerminalArtifact,
type TerminalScreenMode,
} from '../../services/omnl-settlement-terminal';
import {
listTerminalConnectionIds,
loadTerminalConnection,
buildConnectionPublicSummary,
connectionAlchemyApiKey,
} from '../../services/omnl-terminal-connection';
import {
alchemyJsonRpc,
alchemyTokenPricesByAddress,
alchemyPrepareErc20Transfer,
alchemyConfigured,
type AlchemyNetwork,
} from '../../services/omnl-alchemy-client';
import { appendOmnlAudit } from '../../services/omnl-audit-log';
const router = Router();
router.use(omnlRateLimiter);
router.use(omnlAuditMiddleware);
router.use(omnlRequireApiKeyInProduction);
function parseQueryContext(req: Request): {
settlementRef?: string;
officeId?: number;
valueDate?: string;
amountUsd?: string;
connectionId?: string;
} {
const officeRaw = String(req.query.officeId ?? '').trim();
return {
settlementRef: String(req.query.settlementRef ?? '').trim() || undefined,
officeId: officeRaw ? parseInt(officeRaw, 10) : undefined,
valueDate: String(req.query.valueDate ?? '').trim() || undefined,
amountUsd: String(req.query.amountUsd ?? '').trim() || undefined,
connectionId: String(req.query.connectionId ?? '').trim() || undefined,
};
}
function resolveAlchemyKey(connectionId?: string): string | undefined {
if (!connectionId) return undefined;
try {
const profile = loadTerminalConnection(connectionId);
const key = connectionAlchemyApiKey(profile);
return key || undefined;
} catch {
return undefined;
}
}
function parseArtifact(raw: string): TerminalArtifact {
const v = raw.trim().toLowerCase();
const allowed: TerminalArtifact[] = [
'package',
'proof-of-funds',
'debit-note',
'remittance-advice',
'tokenization-sanitized',
'conversion-swift-copy',
];
return (allowed.includes(v as TerminalArtifact) ? v : 'package') as TerminalArtifact;
}
async function withContext(
req: Request,
res: Response,
fn: (ctx: Awaited<ReturnType<typeof buildSettlementTerminalContext>>) => void | Promise<void>
): Promise<void> {
try {
const ctx = await buildSettlementTerminalContext(parseQueryContext(req));
await fn(ctx);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
res.status(500).json({ error: msg });
}
}
router.get('/omnl/terminal/status', omnlSensitiveRouteGuard, (req, res) => {
const connectionId = String(req.query.connectionId ?? '').trim() || undefined;
res.json({
service: 'omnl-settlement-terminal',
version: '1.1.0',
alchemyConfigured: alchemyConfigured(resolveAlchemyKey(connectionId)),
fineractEnv: Boolean(process.env.OMNL_FINERACT_BASE_URL && process.env.OMNL_FINERACT_PASSWORD),
ui: '/omnl/terminal',
connections: listTerminalConnectionIds(),
activeConnectionId: connectionId,
});
});
router.get('/omnl/terminal/connections', omnlSensitiveRouteGuard, (_req, res) => {
const ids = listTerminalConnectionIds();
res.json({
connections: ids.map((id) => {
try {
return buildConnectionPublicSummary(loadTerminalConnection(id));
} catch (e) {
return { connectionId: id, error: e instanceof Error ? e.message : String(e) };
}
}),
});
});
router.get('/omnl/terminal/connections/:connectionId', omnlSensitiveRouteGuard, (req, res) => {
try {
const profile = loadTerminalConnection(String(req.params.connectionId));
res.json(buildConnectionPublicSummary(profile));
} catch (e) {
res.status(404).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.get('/omnl/terminal/package', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
res.json(buildFullTerminalPackage(ctx));
});
});
router.get('/omnl/terminal/proof-of-funds', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
res.json(getTerminalArtifact(ctx, 'proof-of-funds'));
});
});
router.get('/omnl/terminal/debit-note', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
res.json(getTerminalArtifact(ctx, 'debit-note'));
});
});
router.get('/omnl/terminal/remittance-advice', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
res.json(getTerminalArtifact(ctx, 'remittance-advice'));
});
});
router.get('/omnl/terminal/tokenization-sanitized', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
res.json(getTerminalArtifact(ctx, 'tokenization-sanitized'));
});
});
router.get('/omnl/terminal/conversion-swift-copy', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
res.json(getTerminalArtifact(ctx, 'conversion-swift-copy'));
});
});
router.get('/omnl/terminal/screen', omnlSensitiveRouteGuard, async (req, res) => {
await withContext(req, res, (ctx) => {
const modeRaw = String(req.query.mode ?? 'black').trim().toLowerCase();
const mode: TerminalScreenMode = modeRaw === 'color' ? 'color' : 'black';
const artifact = parseArtifact(String(req.query.artifact ?? 'package'));
const text = renderTerminalScreen(ctx, mode, artifact);
const format = String(req.query.format ?? 'json').trim().toLowerCase();
if (format === 'text' || format === 'plain') {
res.type('text/plain; charset=utf-8').send(text);
return;
}
res.json({
mode,
artifact,
text,
context: ctx,
});
});
});
router.post('/omnl/terminal/alchemy/rpc', omnlSensitiveRouteGuard, async (req, res) => {
try {
const network = (String(req.body?.network ?? 'eth-mainnet').trim() ||
'eth-mainnet') as AlchemyNetwork;
const connectionId = String(req.body?.connectionId ?? req.query.connectionId ?? '').trim();
const apiKey = resolveAlchemyKey(connectionId || undefined);
if (!alchemyConfigured(apiKey)) {
res.status(503).json({ error: 'Alchemy API key unset for connection' });
return;
}
const method = String(req.body?.method ?? '').trim();
const params = Array.isArray(req.body?.params) ? req.body.params : [];
if (!method) {
res.status(400).json({ error: 'JSON body must include method' });
return;
}
const result = await alchemyJsonRpc(network, method, params, apiKey);
appendOmnlAudit({
category: 'api',
action: 'alchemy_rpc',
metadata: { network, method },
status: 'ok',
});
res.json({ network, method, result });
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/omnl/terminal/alchemy/prices', omnlSensitiveRouteGuard, async (req, res) => {
try {
const connectionId = String(req.body?.connectionId ?? req.query.connectionId ?? '').trim();
const apiKey = resolveAlchemyKey(connectionId || undefined);
if (!alchemyConfigured(apiKey)) {
res.status(503).json({ error: 'Alchemy API key unset for connection' });
return;
}
const network = (String(req.body?.network ?? 'eth-mainnet').trim() ||
'eth-mainnet') as AlchemyNetwork;
const addresses = Array.isArray(req.body?.addresses) ? req.body.addresses.map(String) : [];
if (!addresses.length) {
res.status(400).json({ error: 'JSON body must include addresses: string[]' });
return;
}
const data = await alchemyTokenPricesByAddress(network, addresses, apiKey);
appendOmnlAudit({
category: 'api',
action: 'alchemy_prices',
metadata: { network, count: addresses.length },
status: 'ok',
});
res.json({ network, data });
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
router.post('/omnl/terminal/alchemy/tx-prep', omnlSensitiveRouteGuard, async (req, res) => {
try {
const connectionId = String(req.body?.connectionId ?? req.query.connectionId ?? '').trim();
const apiKey = resolveAlchemyKey(connectionId || undefined);
if (!alchemyConfigured(apiKey)) {
res.status(503).json({ error: 'Alchemy API key unset for connection' });
return;
}
const network = (String(req.body?.network ?? 'eth-mainnet').trim() ||
'eth-mainnet') as AlchemyNetwork;
const from = String(req.body?.from ?? '').trim();
const to = String(req.body?.to ?? '').trim();
const tokenAddress = String(req.body?.tokenAddress ?? '').trim();
const amountHuman = String(req.body?.amount ?? req.body?.amountHuman ?? '').trim();
const dryRun = req.body?.dryRun !== false;
if (!from || !to || !tokenAddress || !amountHuman) {
res.status(400).json({
error: 'Required: from, to, tokenAddress, amount (human-readable token units)',
});
return;
}
const prep = await alchemyPrepareErc20Transfer({
network,
from,
to,
tokenAddress,
amountHuman,
dryRun,
apiKeyOverride: apiKey,
});
appendOmnlAudit({
category: 'api',
action: 'alchemy_tx_prep',
metadata: { network, from, to, tokenAddress, dryRun },
status: 'ok',
});
res.json(prep);
} catch (e) {
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
}
});
export default router;

View File

@@ -0,0 +1,55 @@
import { Router, Request, Response } from 'express';
import { cacheMiddleware } from '../middleware/cache';
import {
buildProofIndex,
getLatestReserveCapacitySnapshot,
refreshReserveCapacityCache,
} from '../../services/gru-reserve-capacity';
const router = Router();
/**
* GET /reserve/capacity — semi-public live reserve capacity + multi-point proof quorum.
* Public (no OMNL API key). Cached 10s; ?refresh=1 forces recompute when live scheduler enabled.
*/
router.get('/reserve/capacity', cacheMiddleware(10 * 1000), async (req: Request, res: Response) => {
try {
const forceRefresh = req.query.refresh === '1';
let snapshot = getLatestReserveCapacitySnapshot();
const maxAgeSec = snapshot?.sla?.freshnessMaxAgeSeconds ?? 60;
const stale =
!snapshot ||
(Date.now() - new Date(snapshot.generatedAt).getTime()) / 1000 > maxAgeSec;
if (forceRefresh || stale) {
snapshot = await refreshReserveCapacityCache(
String(req.query.set || process.env.GRU_RESERVE_CAPACITY_MINIMUM_SET || 'reserveCapacityLive'),
);
}
if (!snapshot) {
res.status(503).json({
error: 'Reserve capacity snapshot unavailable',
hint: 'Enable GRU_RESERVE_CAPACITY_LIVE=1 or run pnpm gru:reserve-capacity:collect',
});
return;
}
res.setHeader('Cache-Control', 'public, max-age=10, stale-while-revalidate=30');
res.setHeader('X-GRU-Proof-Quorum', snapshot.proofQuorum.passed ? 'pass' : 'fail');
res.status(200).json(snapshot);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
res.status(500).json({ error: msg });
}
});
/**
* GET /reserve/proof-index — status of all configured proof points (P1P7).
*/
router.get('/reserve/proof-index', cacheMiddleware(15 * 1000), (_req: Request, res: Response) => {
res.setHeader('Cache-Control', 'public, max-age=15');
res.json(buildProofIndex());
});
export default router;

View File

@@ -21,7 +21,9 @@ import plannerV2Routes from './routes/planner-v2';
import omnlRoutes from './routes/omnl';
import omnlIpsasRoutes from './routes/omnl-ipsas';
import omnlComplianceRoutes from './routes/omnl-compliance-routes';
import omnlTerminalRoutes from './routes/omnl-terminal-routes';
import checkpointRoutes from './routes/checkpoint';
import reserveCapacityRoutes from './routes/reserve-capacity';
import { MultiChainIndexer } from '../indexer/chain-indexer';
import { OmnlEventPoller } from '../indexer/omnl-event-poller';
import { getDatabasePool } from '../database/client';
@@ -120,12 +122,25 @@ export class ApiServer {
} as const;
this.app.use('/static', express.static(publicPath, staticOpts));
this.app.use('/omnl/static', express.static(publicPath, staticOpts));
this.app.use('/reserve/static', express.static(publicPath, staticOpts));
}
}
private setupRoutes(): void {
// Health check
this.app.get('/health', async (req: Request, res: Response) => {
if (process.env.OMNL_SETTLEMENT_TERMINAL_ONLY === '1') {
res.json({
status: 'healthy',
mode: 'omnl-settlement-terminal-only',
timestamp: new Date().toISOString(),
services: {
database: 'skipped',
indexer: 'disabled',
},
});
return;
}
try {
// Check database connection
const pool = getDatabasePool();
@@ -150,6 +165,9 @@ export class ApiServer {
const dashboardPath = path.join(__dirname, '../../public/omnl-dashboard.html');
const complianceConsolePath = path.join(__dirname, '../../public/omnl-compliance-console.html');
const settlementTerminalPath = path.join(__dirname, '../../public/omnl-settlement-terminal.html');
const reserveDashboardPath = path.join(__dirname, '../../public/reserve-dashboard.html');
const reserveInstitutionalPath = path.join(__dirname, '../../public/reserve-institutional.html');
const authorizeOmnlHtml = (req: Request, res: Response): boolean => {
const tok = process.env.OMNL_DASHBOARD_TOKEN?.trim();
@@ -181,6 +199,34 @@ export class ApiServer {
res.type('html').send(readFileSync(dashboardPath, 'utf8'));
});
this.app.get('/omnl/terminal', (req: Request, res: Response) => {
if (!authorizeOmnlHtml(req, res)) return;
if (!existsSync(settlementTerminalPath)) {
res.status(404).type('text/plain').send('omnl-settlement-terminal.html missing');
return;
}
res.type('html').send(readFileSync(settlementTerminalPath, 'utf8'));
});
/** Public semi-public reserve capacity dashboard (no auth). */
this.app.get('/reserve', (_req: Request, res: Response) => {
if (!existsSync(reserveDashboardPath)) {
res.status(404).type('text/plain').send('reserve-dashboard.html missing');
return;
}
res.type('html').send(readFileSync(reserveDashboardPath, 'utf8'));
});
/** Institutional wrapper (same API; used by reserve.d-bis.org NPM upstream). */
this.app.get('/reserve/institutional', (_req: Request, res: Response) => {
const file = existsSync(reserveInstitutionalPath) ? reserveInstitutionalPath : reserveDashboardPath;
if (!existsSync(file)) {
res.status(404).type('text/plain').send('reserve dashboard missing');
return;
}
res.type('html').send(readFileSync(file, 'utf8'));
});
// Public API catalog (register before routers so GET /api/v1 is not swallowed by middleware-only mounts)
const sendApiV1Catalog = (_req: Request, res: Response) => {
res.json({
@@ -209,6 +255,9 @@ export class ApiServer {
plannerRoutesPlan: '/api/v2/routes/plan',
plannerIntentsPlan: '/api/v2/intents/plan',
plannerInternalExecutionPlan: '/api/v2/routes/internal-execution-plan',
reserveCapacity: '/api/v1/reserve/capacity',
reserveProofIndex: '/api/v1/reserve/proof-index',
reserveDashboard: '/reserve',
},
});
};
@@ -228,7 +277,9 @@ export class ApiServer {
this.app.use('/api/v1', aggregatorRouteMatrixRoutes);
this.app.use('/api/v1', partnerPayloadRoutes);
this.app.use('/api/v1', checkpointRoutes);
this.app.use('/api/v1', reserveCapacityRoutes);
this.app.use('/api/v1', omnlComplianceRoutes);
this.app.use('/api/v1', omnlTerminalRoutes);
this.app.use('/api/v1', omnlRoutes);
this.app.use('/api/v1', omnlIpsasRoutes);
this.app.use('/api/v2', plannerV2Routes);
@@ -248,6 +299,7 @@ export class ApiServer {
omnlOpenApi: '/api/v1/omnl/openapi.json',
omnlCatalog: '/api/v1/omnl/catalog',
omnlDashboard: '/omnl/dashboard',
reserveDashboard: '/reserve',
},
});
});