openvibers/openvibe.billing Commits

Truthful readiness and status: /api/ready answers in the openvibe-shared/ready shape (named db and network_jwks checks, money_writes shown without failing it, release) instead of a bare {ready:true}; STATUS.json describes what production runs (release 421beb4, /metrics, hourly reconciliation, external receipts deployed; shadow import counts; the owner-blocked cutover)

OP OpenVibers committed 24 Sept 2026 · 94f115938f · 3 files, +18 −10


Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex b70b0ba..de1caaf 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -6,7 +6,7 @@
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"adr": "ADR-012",
"authoritative": false,
-- "deployment": "Deployed in shadow on openvibe-ovh: /opt/openvibe.billing (release b59725e), unit openvibe-billing, API on 127.0.0.1:4600 (loopback only; /api/v1 is denied on the public host). Public at https://billing.openvibe.network: the staff console (Network SSO + PKCE, BILLING_STAFF_SUBJECTS = the owner only), /api/health and /webhooks/*. No provider secret is configured, so every provider is off and /webhooks/* answers 404 billing.provider_disabled. Pins openvibe-contracts v0.19.0.",
-- "notes": "Runtime, journal, adapters, import, reconciliation and the staff console (cashout approve/deny, review queue, reconciliation, freeze, audit log) are built and tested. Capabilities (8 billing.*) and events are registered in OpenVibe.Contracts (v0.8.0; billing.staff.action in v0.17.0). Production holds a shadow import of a Live snapshot (322 transactions: 304 imported plus 18 opening adjustments; 3 reconciliation runs, the last 2026-09-23 01:21 UTC, all checks ok), 0 subscriptions and 0 entitlements; the console has never been used (staff_audit 0). The restore drill passed on the host 2026-09-23 05:22 UTC. The Live side (Billing client behind BILLING_AUTHORITY, money freeze) is deployed in Live since c384787, but BILLING_AUTHORITY is not set in live.env, so Live stays the money authority. The cutover (README runbook, docs/live-cutover.md) has not been executed: it waits on the owner putting the PowerChat secret in billing.env and re-pointing the PowerChat webhook to https://billing.openvibe.network/webhooks/powerchat. Built, not yet deployed (after b59725e): /metrics (openvibe-shared v1.3.0, loopback only); hourly scheduled reconciliation stored with its trigger and exposed at GET /api/v1/admin/reconciliations, in the console and on /metrics, with ledger-vs-receipt checks (receipts.ledger, receipts.external, receipts.stale); EXTERNAL PowerChat tips stored in external_receipts and announced as billing.receipt.external under BILLING_AUTHORITY=billing (default live: recorded only), attributed through provider_accounts (imported from Live's powerchat_connections), consumed by OpenVibe.Tips 6d14574; overflow guards (safe integers in the journal, $1M per receipt); settlement under an IMMEDIATE transaction (multi-process races settle once without lock errors). billing.receipt.external is not yet in an openvibe-contracts release. Live's payouts, refunds and plans have no tables of their own; where they map is docs/live-mapping.md.",
-- "updated": "2026-09-23"
++ "deployment": "Deployed in shadow on openvibe-ovh: /opt/openvibe.billing (release 421beb4, openvibe-contracts 0.33.0, openvibe-shared 1.5.1), unit openvibe-billing, API on 127.0.0.1:4600 (loopback only; /api/v1 is denied on the public host). Public at https://billing.openvibe.network: the staff console (Network SSO + PKCE, BILLING_STAFF_SUBJECTS = the owner only), /api/health, /api/ready (openvibe-shared/ready shape), /release.json and /webhooks/*. No provider secret is configured, so every provider is off and /webhooks/* answers 404 billing.provider_disabled.",
++ "notes": "Runtime, journal, adapters, import, reconciliation and the staff console (cashout approve/deny, review queue, reconciliation, freeze, audit log) are built, tested and deployed, with /metrics (loopback only), hourly scheduled reconciliation (GET /api/v1/admin/reconciliations; 16 runs by 2026-09-24 05:06 UTC, ledger-vs-receipt checks), EXTERNAL PowerChat receipts (billing.receipt.external under BILLING_AUTHORITY=billing; recorded only while Live is the authority), overflow guards and IMMEDIATE-transaction settlement. Production holds the shadow import of Live (322 transactions, 996 ledger entries), 0 subscriptions and 0 entitlements; the console has never been used (staff_audit 0). BILLING_AUTHORITY is not set in live.env, so Live stays the money authority. The cutover (README runbook, docs/live-cutover.md) waits on the owner putting the PowerChat secret in billing.env and re-pointing the PowerChat webhook to https://billing.openvibe.network/webhooks/powerchat; a duplicate-webhook proof in production follows it. Live's payouts, refunds and plans have no tables of their own; where they map is docs/live-mapping.md.",
++ "updated": "2026-09-24"
}
ddiff --git a/server/app.js b/server/app.js
iindex 864acf4..0734da6 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -64,13 +64,17 @@ function createApp(opts = {}) {
providers: Object.fromEntries(Object.values(adapters).map((a) => [a.name, a.enabled])),
events_relay: !!config.events.url,
}));
-- app.get('/api/ready', (req, res) => {
-- const problems = [];
-- try { db.prepare('SELECT 1 FROM settings WHERE id = 1').get(); } catch (e) { problems.push(`database: ${e.message}`); }
-- if (!keys.get()) problems.push('Network public key not loaded');
-- if (problems.length) return http.sendProblem(res, 503, 'service.not_ready', { detail: problems.join('; '), ctx: req.ov });
-- return res.json({ ready: true });
++ // Readiness in the openvibe-shared/ready shape (status, named checks, release): 503 when the ledger
++ // database or the Network key fails; the money freeze and the authority show without failing it.
++ const readiness = require('openvibe-shared/ready').createReadiness({
++ service: 'billing', release: release.release,
++ checks: [
++ { name: 'db', required: true, check: () => db.prepare('SELECT 1 AS ok FROM settings WHERE id = 1').get().ok === 1 || 'settings row missing' },
++ { name: 'network_jwks', required: true, check: () => Boolean(keys.get()) || 'Network public key not loaded' },
++ { name: 'money_writes', required: false, check: () => (isFrozen(db) ? 'frozen' : true) },
++ ],
});
++ app.get('/api/ready', readiness.handler);
app.use('/webhooks', webhooksRouter({ ctx, adapters }));
app.use('/api/v1', express.json({ limit: '64kb' }), v1Router({ ctx, auth, adapters }));
ddiff --git a/test/operations.test.js b/test/operations.test.js
iindex 267084a..bbc34db 100644
---- a/test/operations.test.js
++++ b/test/operations.test.js
@@@ -19,7 +19,11 @@ const { boot, fund, check, done } = require('./helpers/app');
assert.deepStrictEqual(h.json.providers, { powerchat: true, stripe: false, paypal: false, ccbill: false, nowpayments: false });
const r = await t.call('GET', '/api/v1/rates', { token: null });
assert.strictEqual(r.json.packages.find((p) => p.bits === 1000).price_cents, 1300);
-- assert.strictEqual((await t.call('GET', '/api/ready', { token: null })).status, 200);
++ const ready = await t.call('GET', '/api/ready', { token: null });
++ assert.strictEqual(ready.status, 200);
++ assert.strictEqual(ready.json.status, 'ready', 'the openvibe-shared/ready shape');
++ assert.strictEqual(ready.json.service, 'billing');
++ assert.ok(ready.json.checks && ready.json.checks.db && ready.json.checks.network_jwks, 'named checks');
const rel = await t.call('GET', '/release.json', { token: null });
assert.strictEqual(rel.status, 200, 'the release manifest (D43) is served');
assert.strictEqual(rel.json.service, 'billing');