Files
wikideck/server/operations-check.ts
T

35 lines
2.7 KiB
TypeScript
Executable File

import { z } from 'zod';
const nonnegative=z.number().finite().nonnegative();
const schema=z.object({schema:z.literal('wikideck-operations-v1'),observedAt:z.string().datetime(),
native:z.object({available:z.boolean(),queued:nonnegative}),
ledger:z.object({height:nonnegative,mutationQueue:nonnegative,tipAgeSeconds:nonnegative.nullable()}),
peers:z.object({configured:nonnegative,connected:nonnegative}),
eventLoop:z.object({meanMs:nonnegative}),disk:z.unknown(),diskSampledAt:nonnegative.nullable(),
market:z.object({queued:nonnegative,lastReconciledAt:nonnegative.nullable(),
orderStates:z.array(z.object({status:z.string(),count:nonnegative})).max(20)}),
custody:z.object({backend:z.string(),mutationHold:z.boolean().optional(),integrityHold:z.boolean().optional(),
audit:z.object({status:z.enum(['ok','failed']),lastAttemptAt:nonnegative,lastSuccessAt:nonnegative,
failureCode:z.string().nullable(),stale:z.boolean()}).optional()}).optional()});
export function checkOperations(raw:unknown,now=Date.now()){
const s=schema.parse(raw),warnings:string[]=[];
const observation=Date.parse(s.observedAt);
if(Math.abs(now-observation)>60000)warnings.push('diagnostics-clock-or-freshness');
if(!s.native.available)warnings.push('native-engine-unavailable');
if(s.native.queued>=48||s.ledger.mutationQueue>=192||s.market.queued>=48)warnings.push('queue-pressure');
if(!s.peers.configured||!s.peers.connected)warnings.push('peer-isolation');
if(s.ledger.height===0||s.ledger.tipAgeSeconds===null||s.ledger.tipAgeSeconds>3600)warnings.push('empty-or-stale-chain');
if(s.eventLoop.meanMs>250)warnings.push('event-loop-delay');
if(!s.market.lastReconciledAt||now-s.market.lastReconciledAt>60000)warnings.push('market-reconciliation-stale');
if(s.market.orderStates.some(r=>(r.status==='review'||r.status==='refund-required')&&r.count>0))warnings.push('market-operator-review');
if(s.custody?.backend==='postgres') {
const audit=s.custody.audit;
if(s.custody.mutationHold||s.custody.integrityHold||audit?.status==='failed')warnings.push('custody-financial-review');
if(!audit||audit.stale||now-audit.lastSuccessAt>120000||audit.lastSuccessAt>now+60000)warnings.push('custody-audit-stale');
}
const disk=z.object({availableBytes:nonnegative}).safeParse(s.disk);
if(!disk.success||!s.diskSampledAt||now-s.diskSampledAt>90000)warnings.push('disk-sample-unavailable-or-stale');
else if(disk.data.availableBytes<512*1024*1024)warnings.push('disk-low');
return {healthy:warnings.length===0,warnings,
note:'Local threshold hints only, not consensus readiness or launch approval. Reorg/rejection counters and native RSS need external time-series monitoring.'};
}