openvibers/openvibe.blog Commits
openvibe-contracts v0.338.0 (was v0.312.0) (#226)
Changes
@@@ -314,7 +314,7 @@ acts for goes in `X-OV-Subject`, and membership still applies:- `blog.feed.read`Browser and app user JWTs are judged by blog membership. The ids and the service manifest are--released in openvibe-contracts (pinned at v0.97.0; the source proposals stay in++released in openvibe-contracts (pinned at v0.338.0; the source proposals stay in[docs/capabilities-proposal/](docs/capabilities-proposal/) and[docs/service-manifest-proposal.json](docs/service-manifest-proposal.json)); `server/auth/capabilities.js`decides them with the contracts grant rule.@@@ -541,7 +541,7 @@ First install (done once; kept for a rebuild):Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).<!-- versions:start -->--- openvibe-contracts: v0.312.0++- openvibe-contracts: v0.338.0- openvibe-sdk: v0.94.0- openvibe-shared: v4.142.0- openvibe-publishing: v1.4.1@@@ -7,12 +7,12 @@"runtime": "node 22, express 4, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9","port": 4810,"domain": "openvibe.blog",-- "contracts": "openvibe-contracts v0.312.0",++ "contracts": "openvibe-contracts v0.338.0","sdk": "openvibe-sdk v0.94.0","shared": "openvibe-shared v4.142.0","packages": ["openvibe-publishing v1.4.1",-- "openvibe-contracts v0.312.0",++ "openvibe-contracts v0.338.0","openvibe-shared v4.142.0","openvibe-sdk v0.94.0"],@@@ -55,5 +55,5 @@"per-actor limits (openvibe-sdk/limits)"],"database": "PostgreSQL ov_blog (ADR-035), schema migrations/0001_initial.sql applied at boot",-- "updated": "2026-11-07"++ "updated": "2026-11-08"}@@@ -15,7 +15,7 @@"express-rate-limit": "^7.5.0","helmet": "^8.1.0","iovalkey": "^0.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.312.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.1","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.142.0",@@@ -1148,9 +1148,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.312.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.312.0",-- "integrity": "sha512-2GsD53zuIedL3eW2LBlYQ68ODTHzoZJHrAmVnZCpiL7ZWnAXGwPW9dpQagLI9Zdkh3LQuuU5ae1YJI0K+acXbA==",++ "version": "0.338.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",++ "integrity": "sha512-75FD6IxMsXoud3NFXKDs0/D0rhJtF2D+WL8M92ArIyabkUnM/sZvw6E6L97EqwQ3O7M/kB+uwsNSxGudbil+ng==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -27,7 +27,7 @@"express-rate-limit": "^7.5.0","helmet": "^8.1.0","iovalkey": "^0.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.312.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.1","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.142.0",@@@ -31,7 +31,9 @@ const CAPABILITIES = Object.freeze({// The ids above as a local fallback while openvibe-contracts does not define one yet (the same// rule the library uses: the exact id, or a `prefix.*` grant covering it). An id the library knows// always goes through the library, so the day a release lands nothing changes here.--const PROPOSED = new Set(Object.values(CAPABILITIES));++// openvibe-contracts v0.338.0 (2026-11-08) released the last one, blog.review.ai_editor, so the++// local fallback is empty and every id above is decided by the library.++const PROPOSED = new Set();/** → { allowed, code, reason } like capabilities.check(). */function checkCapability(claims, capabilityId) {@@@ -1,12 +1,11 @@'use strict';/*** The blog.* capabilities are released by openvibe-contracts, so the service guards delegate to the-- * library's grant rule rather than deciding a proposed id locally. One id is still a proposal and-- * waits for a Contracts release — blog.review.ai_editor (the AI editor's review kind) — so it is-- * decided by the same grant rule locally until then (server/auth/capabilities.js PROPOSED). These-- * pin that: every guarded id is defined by the installed contracts and the blog service manifest,-- * except the proposal, which is tracked as one; and checkCapability agrees with capabilities.check-- * for an exact grant, a prefix grant, no grant, and an id nobody defines.++ * library's grant rule rather than deciding a proposed id locally. Since openvibe-contracts v0.338.0++ * every id this service guards is released — blog.review.ai_editor (the AI editor's review kind)++ * included — so nothing is decided locally any more. These pin that: every guarded id is defined by++ * the installed contracts and the blog service manifest; and checkCapability agrees with++ * capabilities.check for an exact grant, a prefix grant, no grant, and an id nobody defines.*/const assert = require('assert');const { capabilities, services } = require('openvibe-contracts');@@@ -23,21 +22,18 @@ const GRANTED = { cap: ['blog.post.read'], sub: 'svc:openvibe.network' };assert.deepStrictEqual([...manifest.capabilities].sort(), released.sort());for (const id of Object.values(CAPABILITIES)) {const cap = capabilities.get(id);-- if (!cap) {-- assert.ok(PROPOSED.has(id), `${id} is not released yet, so it must be a local proposal`);-- continue;-- }++ assert.ok(cap, `${id} is released by openvibe-contracts`);assert.strictEqual(cap.owner, 'blog', `${id} is owned by blog`);}-- // The AI editor's id is the one proposal; when Contracts releases it, this line goes.-- assert.strictEqual(capabilities.get('blog.review.ai_editor'), undefined);++ // The AI editor's id is released since openvibe-contracts v0.338.0: no id is a local proposal any more.++ assert.strictEqual(PROPOSED.size, 0, 'no capability is waiting for a Contracts release');});await check('checkCapability follows the contracts grant rule (exact, prefix, denied, unknown)', async () => {// An exact grant and a `prefix.*` grant the library's matching rule accepts.assert.deepStrictEqual(checkCapability(GRANTED, 'blog.post.read'), { allowed: true, code: null, reason: null });assert.deepStrictEqual(checkCapability({ cap: ['blog.*'] }, 'blog.post.read'), { allowed: true, code: null, reason: null });-- // The proposal is decided with the same rule until Contracts defines it.++ // Every id is released, so the library's own rule decides it.assert.deepStrictEqual(checkCapability({ cap: ['blog.review.ai_editor'] }, 'blog.review.ai_editor'), { allowed: true, code: null, reason: null });assert.deepStrictEqual(checkCapability({ cap: ['blog.*'] }, 'blog.review.ai_editor'), { allowed: true, code: null, reason: null });assert.strictEqual(checkCapability({ cap: ['blog.post.publish'] }, 'blog.review.ai_editor').code, 'capability.denied');
@@ -314,7 +314,7 @@ acts for goes in `X-OV-Subject`, and membership still applies:
@@ -314,7 +314,7 @@ acts for goes in `X-OV-Subject`, and membership still applies:
- `blog.feed.read`
- `blog.feed.read`
Browser and app user JWTs are judged by blog membership. The ids and the service manifest are
Browser and app user JWTs are judged by blog membership. The ids and the service manifest are
-released in openvibe-contracts (pinned at v0.97.0; the source proposals stay in
+released in openvibe-contracts (pinned at v0.338.0; the source proposals stay in
[docs/capabilities-proposal/](docs/capabilities-proposal/) and
[docs/capabilities-proposal/](docs/capabilities-proposal/) and
[docs/service-manifest-proposal.json](docs/service-manifest-proposal.json)); `server/auth/capabilities.js`
[docs/service-manifest-proposal.json](docs/service-manifest-proposal.json)); `server/auth/capabilities.js`
decides them with the contracts grant rule.
decides them with the contracts grant rule.
@@ -541,7 +541,7 @@ First install (done once; kept for a rebuild):
@@ -541,7 +541,7 @@ First install (done once; kept for a rebuild):
Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).
Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).
<!-- versions:start -->
<!-- versions:start -->
-- openvibe-contracts: v0.312.0
+- openvibe-contracts: v0.338.0
- openvibe-sdk: v0.94.0
- openvibe-sdk: v0.94.0
- openvibe-shared: v4.142.0
- openvibe-shared: v4.142.0
- openvibe-publishing: v1.4.1
- openvibe-publishing: v1.4.1
@@ -7,12 +7,12 @@
@@ -7,12 +7,12 @@
"runtime": "node 22, express 4, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"runtime": "node 22, express 4, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4810,
"port": 4810,
"domain": "openvibe.blog",
"domain": "openvibe.blog",
- "contracts": "openvibe-contracts v0.312.0",
+ "contracts": "openvibe-contracts v0.338.0",
"sdk": "openvibe-sdk v0.94.0",
"sdk": "openvibe-sdk v0.94.0",
"shared": "openvibe-shared v4.142.0",
"shared": "openvibe-shared v4.142.0",
"packages": [
"packages": [
"openvibe-publishing v1.4.1",
"openvibe-publishing v1.4.1",
- "openvibe-contracts v0.312.0",
+ "openvibe-contracts v0.338.0",
"openvibe-shared v4.142.0",
"openvibe-shared v4.142.0",
"openvibe-sdk v0.94.0"
"openvibe-sdk v0.94.0"
],
],
@@ -55,5 +55,5 @@
@@ -55,5 +55,5 @@
"per-actor limits (openvibe-sdk/limits)"
"per-actor limits (openvibe-sdk/limits)"
],
],
"database": "PostgreSQL ov_blog (ADR-035), schema migrations/0001_initial.sql applied at boot",
"database": "PostgreSQL ov_blog (ADR-035), schema migrations/0001_initial.sql applied at boot",
- "updated": "2026-11-07"
+ "updated": "2026-11-08"
}
}
@@ -15,7 +15,7 @@
@@ -15,7 +15,7 @@
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.312.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.1",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.142.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.142.0",
@@ -1148,9 +1148,9 @@
@@ -1148,9 +1148,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.312.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.312.0",
- "integrity": "sha512-2GsD53zuIedL3eW2LBlYQ68ODTHzoZJHrAmVnZCpiL7ZWnAXGwPW9dpQagLI9Zdkh3LQuuU5ae1YJI0K+acXbA==",
+ "version": "0.338.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
+ "integrity": "sha512-75FD6IxMsXoud3NFXKDs0/D0rhJtF2D+WL8M92ArIyabkUnM/sZvw6E6L97EqwQ3O7M/kB+uwsNSxGudbil+ng==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.312.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.1",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.142.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.142.0",
@@ -31,7 +31,9 @@ const CAPABILITIES = Object.freeze({
@@ -31,7 +31,9 @@ const CAPABILITIES = Object.freeze({
// The ids above as a local fallback while openvibe-contracts does not define one yet (the same
// The ids above as a local fallback while openvibe-contracts does not define one yet (the same
// rule the library uses: the exact id, or a `prefix.*` grant covering it). An id the library knows
// rule the library uses: the exact id, or a `prefix.*` grant covering it). An id the library knows
// always goes through the library, so the day a release lands nothing changes here.
// always goes through the library, so the day a release lands nothing changes here.
-const PROPOSED = new Set(Object.values(CAPABILITIES));
+// openvibe-contracts v0.338.0 (2026-11-08) released the last one, blog.review.ai_editor, so the
+// local fallback is empty and every id above is decided by the library.
+const PROPOSED = new Set();
/** → { allowed, code, reason } like capabilities.check(). */
/** → { allowed, code, reason } like capabilities.check(). */
function checkCapability(claims, capabilityId) {
function checkCapability(claims, capabilityId) {
@@ -1,12 +1,11 @@
@@ -1,12 +1,11 @@
'use strict';
'use strict';
/**
/**
* The blog.* capabilities are released by openvibe-contracts, so the service guards delegate to the
* The blog.* capabilities are released by openvibe-contracts, so the service guards delegate to the
- * library's grant rule rather than deciding a proposed id locally. One id is still a proposal and
- * waits for a Contracts release — blog.review.ai_editor (the AI editor's review kind) — so it is
- * decided by the same grant rule locally until then (server/auth/capabilities.js PROPOSED). These
- * pin that: every guarded id is defined by the installed contracts and the blog service manifest,
- * except the proposal, which is tracked as one; and checkCapability agrees with capabilities.check
- * for an exact grant, a prefix grant, no grant, and an id nobody defines.
+ * library's grant rule rather than deciding a proposed id locally. Since openvibe-contracts v0.338.0
+ * every id this service guards is released — blog.review.ai_editor (the AI editor's review kind)
+ * included — so nothing is decided locally any more. These pin that: every guarded id is defined by
+ * the installed contracts and the blog service manifest; and checkCapability agrees with
+ * capabilities.check for an exact grant, a prefix grant, no grant, and an id nobody defines.
*/
*/
const assert = require('assert');
const assert = require('assert');
const { capabilities, services } = require('openvibe-contracts');
const { capabilities, services } = require('openvibe-contracts');
@@ -23,21 +22,18 @@ const GRANTED = { cap: ['blog.post.read'], sub: 'svc:openvibe.network' };
@@ -23,21 +22,18 @@ const GRANTED = { cap: ['blog.post.read'], sub: 'svc:openvibe.network' };
assert.deepStrictEqual([...manifest.capabilities].sort(), released.sort());
assert.deepStrictEqual([...manifest.capabilities].sort(), released.sort());
for (const id of Object.values(CAPABILITIES)) {
for (const id of Object.values(CAPABILITIES)) {
const cap = capabilities.get(id);
const cap = capabilities.get(id);
- if (!cap) {
- assert.ok(PROPOSED.has(id), `${id} is not released yet, so it must be a local proposal`);
- continue;
- }
+ assert.ok(cap, `${id} is released by openvibe-contracts`);
assert.strictEqual(cap.owner, 'blog', `${id} is owned by blog`);
assert.strictEqual(cap.owner, 'blog', `${id} is owned by blog`);
}
}
- // The AI editor's id is the one proposal; when Contracts releases it, this line goes.
- assert.strictEqual(capabilities.get('blog.review.ai_editor'), undefined);
+ // The AI editor's id is released since openvibe-contracts v0.338.0: no id is a local proposal any more.
+ assert.strictEqual(PROPOSED.size, 0, 'no capability is waiting for a Contracts release');
});
});
await check('checkCapability follows the contracts grant rule (exact, prefix, denied, unknown)', async () => {
await check('checkCapability follows the contracts grant rule (exact, prefix, denied, unknown)', async () => {
// An exact grant and a `prefix.*` grant the library's matching rule accepts.
// An exact grant and a `prefix.*` grant the library's matching rule accepts.
assert.deepStrictEqual(checkCapability(GRANTED, 'blog.post.read'), { allowed: true, code: null, reason: null });
assert.deepStrictEqual(checkCapability(GRANTED, 'blog.post.read'), { allowed: true, code: null, reason: null });
assert.deepStrictEqual(checkCapability({ cap: ['blog.*'] }, 'blog.post.read'), { allowed: true, code: null, reason: null });
assert.deepStrictEqual(checkCapability({ cap: ['blog.*'] }, 'blog.post.read'), { allowed: true, code: null, reason: null });
- // The proposal is decided with the same rule until Contracts defines it.
+ // Every id is released, so the library's own rule decides it.
assert.deepStrictEqual(checkCapability({ cap: ['blog.review.ai_editor'] }, 'blog.review.ai_editor'), { allowed: true, code: null, reason: null });
assert.deepStrictEqual(checkCapability({ cap: ['blog.review.ai_editor'] }, 'blog.review.ai_editor'), { allowed: true, code: null, reason: null });
assert.deepStrictEqual(checkCapability({ cap: ['blog.*'] }, 'blog.review.ai_editor'), { allowed: true, code: null, reason: null });
assert.deepStrictEqual(checkCapability({ cap: ['blog.*'] }, 'blog.review.ai_editor'), { allowed: true, code: null, reason: null });
assert.strictEqual(checkCapability({ cap: ['blog.post.publish'] }, 'blog.review.ai_editor').code, 'capability.denied');
assert.strictEqual(checkCapability({ cap: ['blog.post.publish'] }, 'blog.review.ai_editor').code, 'capability.denied');