Milestone 0: import zappier billing, Verae middleware, and Zapier research

Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
This commit is contained in:
George Lambert 2026-09-09 02:37:36 -04:00
commit b4150c8250
1364 changed files with 6814366 additions and 0 deletions

View file

@ -0,0 +1,287 @@
/**
* @fileoverview HTTP client for api.veraetime.net (with mock mode).
* @module clients/veraeClient
*/
import { randomUUID } from 'node:crypto';
import { config } from '../config.js';
import { AppError } from '../errors.js';
import { createDebugger } from '../debug/logger.js';
const log = createDebugger('http');
const mockJobs = new Map();
/**
* @param {number} ms
* @returns {Promise<void>}
*/
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function mockLogin({ username, password }) {
if (!username || !password) {
throw new AppError('Invalid credentials', { status: 401, code: 'UNAUTHORIZED' });
}
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
return {
token: `mock-jwt-${username}`,
expiresAt,
user: {
id: randomUUID(),
username,
role: username.includes('admin') ? 'admin' : 'user',
},
};
}
async function mockValidate(token) {
if (!token?.startsWith('mock-jwt-')) {
throw new AppError('Invalid or expired token', { status: 401, code: 'UNAUTHORIZED' });
}
const username = token.replace('mock-jwt-', '');
return {
valid: true,
userId: randomUUID(),
username,
role: 'user',
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
};
}
async function mockCreateTimestamp({ data, hashAlg }) {
if (!data) {
throw new AppError('Invalid input data', { status: 400, code: 'VALIDATION_ERROR' });
}
const jobId = randomUUID();
mockJobs.set(jobId, {
id: jobId,
status: 'pending',
createdAt: Date.now(),
data,
hashAlg: hashAlg ?? 'SHA256',
});
setTimeout(() => {
const job = mockJobs.get(jobId);
if (!job) return;
job.status = 'completed';
job.result = `mock-cert-${jobId}`;
job.completedAt = new Date().toISOString();
job.metadata = {
blockIndex: 42,
timestamp: job.completedAt,
certificate: job.result,
};
}, 150);
return { jobId };
}
async function mockGetStatus(jobId) {
const job = mockJobs.get(jobId);
if (!job) {
throw new AppError('Job not found', { status: 404, code: 'NOT_FOUND' });
}
return {
id: job.id,
status: job.status,
result: job.result,
completedAt: job.completedAt,
metadata: job.metadata,
error: job.error,
};
}
async function mockVerify({ certificate }) {
if (!certificate) {
throw new AppError('Invalid input data', { status: 400, code: 'VALIDATION_ERROR' });
}
const valid = certificate.startsWith('mock-cert-') || certificate.startsWith('eyJ');
return valid
? { valid: true, timestamp: new Date().toISOString(), blockIndex: 42 }
: { valid: false };
}
/**
* Low-level fetch to Verae API.
* @param {string} path
* @param {{ method?: string, token?: string, body?: unknown }} [options]
* @returns {Promise<any>}
*/
async function request(path, { method = 'GET', token, body } = {}) {
const url = `${config.veraeApiBaseUrl}${path}`;
const headers = { Accept: 'application/json' };
if (token) {
headers.Authorization = `Bearer ${token}`;
}
if (body !== undefined) {
headers['Content-Type'] = 'application/json';
}
const started = Date.now();
log.debug('verae request', { method, path, hasToken: Boolean(token) });
const response = await fetch(url, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
});
let payload = null;
const text = await response.text();
if (text) {
try {
payload = JSON.parse(text);
} catch {
payload = { error: text };
}
}
log.debug('verae response', {
method,
path,
status: response.status,
durationMs: Date.now() - started,
});
if (!response.ok) {
throw new AppError(payload?.error ?? `Verae API error (${response.status})`, {
status: response.status,
code: payload?.code ?? 'VERAE_API_ERROR',
details: payload,
});
}
return payload;
}
/**
* Verae API client (mock when config.mockVerae is true).
*/
export const veraeClient = {
/**
* @param {{ username: string, password: string }} credentials
*/
async login(credentials) {
if (config.mockVerae) return mockLogin(credentials);
return request('/auth/login', { method: 'POST', body: credentials });
},
/**
* @param {string} token
*/
async validate(token) {
if (config.mockVerae) return mockValidate(token);
return request('/auth/validate', { token });
},
/**
* @param {string} token
* @param {{ data: string, hashAlg?: string }} body
*/
async createTimestamp(token, body) {
if (config.mockVerae) return mockCreateTimestamp(body);
return request('/api/timestamp', { method: 'POST', token, body });
},
/**
* @param {string} token
* @param {{ items: Array<{ data: string, hashAlg?: string }> }} body
*/
async createBatchTimestamp(token, body) {
if (config.mockVerae) {
const jobIds = [];
for (const item of body.items ?? []) {
const res = await mockCreateTimestamp(item);
jobIds.push(res.jobId);
}
return { jobIds };
}
return request('/api/batch/timestamp', { method: 'POST', token, body });
},
/**
* @param {string} token
* @param {string} jobId
*/
async getStatus(token, jobId) {
if (config.mockVerae) return mockGetStatus(jobId);
return request(`/api/status/${encodeURIComponent(jobId)}`, { token });
},
/**
* @param {string} token
* @param {{ jobIds: string[] }} body
*/
async getBatchStatus(token, body) {
if (config.mockVerae) {
const results = {};
for (const jobId of body.jobIds ?? []) {
results[jobId] = await mockGetStatus(jobId);
}
return { results };
}
return request('/api/batch/status', { method: 'POST', token, body });
},
/**
* @param {string} token
* @param {{ certificate: string }} body
*/
async verify(token, body) {
if (config.mockVerae) return mockVerify(body);
return request('/api/verify', { method: 'POST', token, body });
},
/**
* @param {string} token
* @param {{ certificates: string[] }} body
*/
async verifyBatch(token, body) {
if (config.mockVerae) {
const results = [];
for (const certificate of body.certificates ?? []) {
results.push(await mockVerify({ certificate }));
}
return { results };
}
return request('/api/batch/verify', { method: 'POST', token, body });
},
/**
* @param {string} token
* @param {string} jobId
*/
async getJobVerification(token, jobId) {
if (config.mockVerae) return mockGetStatus(jobId);
return request(`/api/verify/${encodeURIComponent(jobId)}`, { token });
},
/**
* Poll until completed/failed or timeout.
* @param {string} token
* @param {string} jobId
* @param {{ maxAttempts: number, intervalMs: number }} options
*/
async waitForJob(token, jobId, { maxAttempts, intervalMs }) {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const status = await this.getStatus(token, jobId);
if (status.status === 'completed' || status.status === 'failed') {
return status;
}
await delay(intervalMs);
}
throw new AppError(`Job ${jobId} timed out`, { status: 504, code: 'GATEWAY_TIMEOUT' });
},
};
/**
* Clear mock jobs (tests only).
* @returns {void}
*/
export function clearMockJobs() {
mockJobs.clear();
}