Milestone 3: NATS wait (gate 9), hash lookup, zappier timestamp, NS1 tunnel

Wait-via-NATS returns completed or pending+jobId (GATE 9). Mock SHA256
idempotent register + GET /hashes/{sha256}. Zappier commercial edge has
POST /v1/timestamp and hash-lookup. GATE 12 smoke (signup + wait) passes.

NS1 NATS is 127.0.0.1:4222 on 70.88.205.138; SSH tunnel :14222. Local
nats-server -js used for isolated tests. Activate app adds Echo Text.

Learned: JetStream on NS1 is loopback-only; do not bind 4222 public.
This commit is contained in:
George Lambert 2026-09-09 02:54:02 -04:00
parent 10c663cc0c
commit 51ae79b75f
75 changed files with 1114 additions and 123 deletions

View file

@ -3,7 +3,7 @@
* @module clients/veraeClient
*/
import { randomUUID } from 'node:crypto';
import { createHash, randomUUID } from 'node:crypto';
import { config } from '../config.js';
import { AppError } from '../errors.js';
import { createDebugger } from '../debug/logger.js';
@ -11,6 +11,16 @@ import { createDebugger } from '../debug/logger.js';
const log = createDebugger('http');
const mockJobs = new Map();
/** @type {Map<string, { jobId: string, sha256: string, timestamp?: string, publicMetadata?: object, privateMetadata?: object }>} */
const mockHashes = new Map();
/**
* @param {string} data
* @returns {string}
*/
export function sha256Hex(data) {
return createHash('sha256').update(String(data), 'utf8').digest('hex');
}
/**
* @param {number} ms
@ -50,19 +60,36 @@ async function mockValidate(token) {
};
}
async function mockCreateTimestamp({ data, hashAlg }) {
if (!data) {
async function mockCreateTimestamp({ data, hashAlg, sha256, publicMetadata, privateMetadata }) {
if (!data && !sha256) {
throw new AppError('Invalid input data', { status: 400, code: 'VALIDATION_ERROR' });
}
const key = (sha256 || sha256Hex(data)).toLowerCase();
const existing = mockHashes.get(key);
if (existing) {
const job = mockJobs.get(existing.jobId);
return {
jobId: existing.jobId,
sha256: key,
existing: true,
timestamp: job?.completedAt ?? existing.timestamp,
};
}
const jobId = randomUUID();
mockJobs.set(jobId, {
id: jobId,
status: 'pending',
createdAt: Date.now(),
data,
data: data ?? key,
hashAlg: hashAlg ?? 'SHA256',
sha256: key,
publicMetadata: publicMetadata ?? {},
privateMetadata: privateMetadata ?? {},
});
mockHashes.set(key, { jobId, sha256: key, publicMetadata, privateMetadata });
const delayMs = config.mockJobCompleteMs ?? 150;
setTimeout(() => {
const job = mockJobs.get(jobId);
if (!job) return;
@ -73,10 +100,14 @@ async function mockCreateTimestamp({ data, hashAlg }) {
blockIndex: 42,
timestamp: job.completedAt,
certificate: job.result,
sha256: key,
publicMetadata: job.publicMetadata,
};
}, 150);
const rec = mockHashes.get(key);
if (rec) rec.timestamp = job.completedAt;
}, delayMs);
return { jobId };
return { jobId, sha256: key, existing: false };
}
async function mockGetStatus(jobId) {
@ -91,6 +122,24 @@ async function mockGetStatus(jobId) {
completedAt: job.completedAt,
metadata: job.metadata,
error: job.error,
sha256: job.sha256,
};
}
async function mockLookupHash(sha256) {
const key = String(sha256 || '').toLowerCase();
const rec = mockHashes.get(key);
if (!rec) {
throw new AppError('Hash not found', { status: 404, code: 'NOT_FOUND' });
}
const job = mockJobs.get(rec.jobId);
return {
sha256: key,
exists: true,
jobId: rec.jobId,
timestamp: rec.timestamp ?? job?.completedAt,
status: job?.status,
publicMetadata: job?.publicMetadata ?? rec.publicMetadata ?? {},
};
}
@ -184,7 +233,20 @@ export const veraeClient = {
*/
async createTimestamp(token, body) {
if (config.mockVerae) return mockCreateTimestamp(body);
return request('/api/timestamp', { method: 'POST', token, body });
const liveBody = { data: body.data, hashAlg: body.hashAlg };
return request('/api/timestamp', { method: 'POST', token, body: liveBody });
},
/**
* @param {string} token
* @param {string} sha256
*/
async lookupHash(token, sha256) {
if (config.mockVerae) return mockLookupHash(sha256);
throw new AppError('Hash lookup is not on the live Verae OpenAPI', {
status: 501,
code: 'NOT_IMPLEMENTED',
});
},
/**
@ -284,4 +346,5 @@ export const veraeClient = {
*/
export function clearMockJobs() {
mockJobs.clear();
mockHashes.clear();
}