openvibers/openvibe.network Commits

Expire approved-unused confirmations on cancel and drop Trade Events…

OP OpenVibers committed 4 Oct 2026 · 2dbfa1ad00 · 4 files, +6 −12

Requested by finish-f1. Handed to the local harness by the cloud job pr-OpenVibe.Network-38; done by DeepSeek harness.

Changes

ddiff --git a/server/developer/confirmations.js b/server/developer/confirmations.js
iindex 84e6b2cf..3f039f9d 100644
---- a/server/developer/confirmations.js
++++ b/server/developer/confirmations.js
@@@ -40,8 +40,8 @@ const LIVE = "(state = 'pending' OR (state = 'approved' AND used_at IS NULL))";
const fail = (status, code, detail) => { throw new store.DevError(status, code, detail); };
const nowIso = () => new Date().toISOString();
--/** The state a read reports: a pending row past expires_at is expired before the sweep records it. */
--const stateOf = (c, now = nowIso()) => (c.state === 'pending' && c.expires_at <= now ? 'expired' : c.state);
++/** The state a read reports: a live row past expires_at (pending, or approved and unused) is expired before the sweep records it. */
++const stateOf = (c, now = nowIso()) => ((c.state === 'pending' || (c.state === 'approved' && !c.used_at)) && c.expires_at <= now ? 'expired' : c.state);
/** network.confirmation-request@1: owner and requested_by rebuilt as SubjectRefs, details/resources parsed. */
function confirmationView(c) {
@@@ -186,9 +186,9 @@ async function read(db, { id, audience }) {
}
/**
-- * The owning service withdraws one it created: a pending or approved-unused row becomes cancelled (reason `service`);
-- * one already cancelled is returned as it is; a used, denied or expired one is 409 confirmation.not_pending. The
-- * owner's standing rules are left alone. → { confirmation }
++ * The owning service withdraws one it created: a pending or approved-unused row still inside its ttl becomes cancelled
++ * (reason `service`); one already cancelled is returned as it is; a used, denied or expired (past `expires_at`) one is
++ * 409 confirmation.not_pending. The owner's standing rules are left alone. → { confirmation }
*/
async function cancel(db, { id, audience }, { ctx } = {}) {
await db.tx(async () => {
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex e46923a0..bf0f0100 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -130,8 +130,6 @@ const DEFAULT_GRANTS = [
['trade', 'events.event.publish', 'openvibe.events', []],
['trade', 'sources.item.read', 'openvibe.sources', []],
['trade', 'sources.source.read', 'openvibe.sources', []],
-- // Trade subscribes to the Sources filings it follows (plan T9 step 1), as Coupons and Deals do.
-- ['trade', 'events.subscription.manage', 'openvibe.events', []],
// Wave 18: Deals and Coupons; Wave 21 Stage B: the Host API publishes deploy/domain events.
['deals', 'identity.subject.resolve', SELF_AUDIENCE, []],
['deals', 'events.event.publish', 'openvibe.events', []],
ddiff --git a/test/confirmations.test.js b/test/confirmations.test.js
iindex 641f49bb..454cc03d 100644
---- a/test/confirmations.test.js
++++ b/test/confirmations.test.js
@@@ -399,6 +399,7 @@ for (const [id, name] of PEOPLE) T[name] = jwt.sign({ sub: id, id }, keys.privat
await approve(i2);
await db.prepare("UPDATE dev_confirmations SET expires_at = '2001-01-01T00:00:00.000Z' WHERE id = ?").run(i2.id);
refused(await internal('media', 'POST', `/${i2.id}/consume`, { request_digest: D }), 409, 'confirmation.expired');
++ refused(await internal('media', 'POST', `/${i2.id}/cancel`, {}), 409, 'confirmation.not_pending');
const i3 = ok(await internal('media', 'POST', '', askBody()), 201).confirmation;
await db.prepare("UPDATE dev_confirmations SET expires_at = '2001-01-01T00:00:00.000Z' WHERE id = ?").run(i3.id);
refused(await internal('media', 'POST', `/${i3.id}/cancel`, {}), 409, 'confirmation.not_pending');
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex dae058f0..8fc7b1be 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -167,11 +167,6 @@ const server = http.createServer(app);
const cps = await token({ client_id: 'coupons', client_secret: 'coupons-secret', audience: 'openvibe.events', scope: 'events.subscription.manage' });
assert.strictEqual(cps.status, 200, JSON.stringify(cps.body));
assert.strictEqual(cps.body.scope, 'events.subscription.manage', 'Coupons manages its Events subscriptions');
-- // Trade subscribes to the Sources filings it follows (plan T9 step 1).
-- await db.prepare("UPDATE oauth_clients SET client_secret = 'trade-secret' WHERE client_id = 'trade'").run();
-- const trs = await token({ client_id: 'trade', client_secret: 'trade-secret', audience: 'openvibe.events', scope: 'events.subscription.manage' });
-- assert.strictEqual(trs.status, 200, JSON.stringify(trs.body));
-- assert.strictEqual(trs.body.scope, 'events.subscription.manage', 'Trade manages its Events subscriptions');
// Go-live fan-out accepts Live's token (network.notifications.push); a narrower token is refused.
r = await post('/internal/events/stream-live', {}, { authorization: `Bearer ${full}` });