Add customer names on the ledger, staff session login, and exclusive jobs.events.
Some checks are pending
offline / test (push) Waiting to run

Account-balance stores display names and looks up by name. Edge writes
names on customer create/edit; staff UIs join from edge when needed.
New verae-staff-session issues a host cookie; department HTML redirects
when STAFF_AUTH=1. JOBS_EVENTS_EXCLUSIVE lets jobs-events own the durable
consumer. Catalog index is cards; disabled fleet machines are grey.
This commit is contained in:
George Lambert 2026-09-11 18:11:21 -04:00
parent cb07f5b321
commit 9cc0018708
38 changed files with 533 additions and 68 deletions

View file

@ -1,3 +1,5 @@
# NATS — verae-jobs-events
IN `verae.zapier.jobs.events` queue `jobs-events`.
When `JOBS_EVENTS_EXCLUSIVE=1`, this process is the durable JetStream consumer `jobs-events-exclusive` on `ZAPIER_EVENTS` and republishes to `verae.internal.jobs.events`. Middleware must set the same env so it does not also consume `verae.zapier.jobs.events`.

View file

@ -14,14 +14,40 @@ async function startNats() {
const { connect, StringCodec } = await import('nats');
const nc = await connect({ servers: url.split(','), name: 'verae-jobs-events' });
const sc = StringCodec();
for await (const m of nc.subscribe(SUBJECTS.IN, { queue: SUBJECTS.QUEUE })) {
const exclusive = process.env.JOBS_EVENTS_EXCLUSIVE === '1';
const deliver = async (m) => {
processed += 1;
try {
last = JSON.parse(sc.decode(m.data) || '{}');
} catch {
last = {};
}
if (m.reply) m.respond(sc.encode(JSON.stringify({ ok: true, processed })));
if (exclusive) {
nc.publish(SUBJECTS.INTERNAL, m.data);
}
if (m.reply) m.respond(sc.encode(JSON.stringify({ ok: true, processed, exclusive })));
if (typeof m.ack === 'function') await m.ack();
};
if (exclusive) {
try {
const js = nc.jetstream();
const jsm = await nc.jetstreamManager();
await jsm.consumers.add('ZAPIER_EVENTS', {
durable_name: SUBJECTS.DURABLE,
ack_policy: 'explicit',
filter_subject: SUBJECTS.IN,
max_deliver: 10,
}).catch(() => {});
const consumer = await js.consumers.get('ZAPIER_EVENTS', SUBJECTS.DURABLE);
const messages = await consumer.consume({ max_messages: 10 });
for await (const m of messages) await deliver(m);
return;
} catch (err) {
process.stderr.write(`js exclusive fallback core sub: ${err.message}\n`);
}
}
for await (const m of nc.subscribe(SUBJECTS.IN, { queue: SUBJECTS.QUEUE })) {
await deliver(m);
}
}
@ -29,7 +55,14 @@ const server = http.createServer((req, res) => {
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
if (url.pathname === '/health') {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ ok: true, role: 'verae-jobs-events', processed, subject: SUBJECTS.IN, lastEvent: last?.event || null }));
res.end(JSON.stringify({
ok: true,
role: 'verae-jobs-events',
processed,
subject: SUBJECTS.IN,
exclusive: process.env.JOBS_EVENTS_EXCLUSIVE === '1',
lastEvent: last?.event || null,
}));
return;
}
res.writeHead(404, { 'content-type': 'application/json' });

View file

@ -1,4 +1,6 @@
export const SUBJECTS = {
IN: 'verae.zapier.jobs.events',
INTERNAL: 'verae.internal.jobs.events',
QUEUE: 'jobs-events',
DURABLE: 'jobs-events-exclusive',
};