openvibers/openvibe.contracts Commits
Compat gate: recorded corrections (compatibility/corrections.json), accepted against their one base tag only; the 0.48.0 correction of network.grant.changed (the 0.47.0 shape nothing produced or consumed) is the first
Changes
@@@ -6,7 +6,7 @@ the notes were in the tag and commit messages (`git tag -n1`).## 0.48.0 — 2026-09-25--**Correction to 0.47.0.** `network.grant.changed` was already emitted by Network's developer event relay for a developer app's grant, and OpenVibe.Events acts on it (it stops an app's subscriptions when its `events.app.subscribe` grant leaves approved). 0.47.0 gave that event a service-principal payload that does not match. Now `network.grant.changed` describes what is actually sent: `{ project_id, capability, audience, from, to }`, subject `app`. A first-party service principal's grant change is the new **`network.principal_grant.changed`** (the 0.47.0 payload under its own name). Nothing consumed the 0.47.0 shape.++**Correction to 0.47.0.** `network.grant.changed` was already emitted by Network's developer event relay for a developer app's grant, and OpenVibe.Events acts on it (it stops an app's subscriptions when its `events.app.subscribe` grant leaves approved). 0.47.0 gave that event a service-principal payload that does not match. Now `network.grant.changed` describes what is actually sent: `{ project_id, capability, audience, from, to }`, subject `app`. A first-party service principal's grant change is the new **`network.principal_grant.changed`** (the 0.47.0 payload under its own name). Nothing consumed the 0.47.0 shape. The compat gate records this in `compatibility/corrections.json` (accepted against v0.47.0 only).## 0.47.0 — 2026-09-25nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "_about": "Recorded corrections: a contract released with a schema that nothing ever produced or consumed, fixed in the next release to describe what is actually sent. { id, base (the release tag the correction is compared with), fixedIn, reason }. The compat gate accepts the listed contract's changes against that one base only; every other comparison, and every later release, is checked as usual. Never for a contract anyone implemented.",++ "corrections": [++ {++ "id": "network.grant.changed",++ "base": "v0.47.0",++ "fixedIn": "0.48.0",++ "reason": "0.47.0 gave network.grant.changed a service-principal payload, but Network's developer relay already sent it for developer-app grants ({ project_id, capability, audience, from, to }) and OpenVibe.Events acts on that shape. Nothing produced or consumed the 0.47.0 shape; it moved to network.principal_grant.changed."++ }++ ]++}@@@ -43,10 +43,13 @@ function compare(id, a, b, at) {const oldCat = readAt(base, 'contracts/catalog.json');const newCat = JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts/catalog.json'), 'utf8'));const deprecated = new Set(JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/deprecations.json'), 'utf8')).deprecations.map(d => d.id));++// A recorded correction (compatibility/corrections.json) is accepted against its one base tag only.++const corrected = new Set(JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/corrections.json'), 'utf8')).corrections.filter(c => c.base === base).map(c => c.id));for (const entry of (oldCat ? oldCat.contracts : [])) {const now = newCat.contracts.find(c => c.id === entry.id);if (!now) { if (!deprecated.has(entry.id)) problems.push(`${entry.id}: removed from the catalog without a deprecation`); continue; }if (now.schema !== entry.schema) continue; // new major file; old one must still be listed separately++ if (corrected.has(entry.id)) { console.log(`compat vs ${base}: ${entry.id} is a recorded correction (compatibility/corrections.json)`); continue; }compare(entry.id, readAt(base, `contracts/${entry.schema}`), JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', entry.schema), 'utf8')), '');}if (problems.length) { console.error(`compat vs ${base}: ${problems.length} breaking change(s)\n ${problems.join('\n ')}`); process.exit(1); }
@@ -6,7 +6,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
@@ -6,7 +6,7 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.48.0 — 2026-09-25
## 0.48.0 — 2026-09-25
-**Correction to 0.47.0.** `network.grant.changed` was already emitted by Network's developer event relay for a developer app's grant, and OpenVibe.Events acts on it (it stops an app's subscriptions when its `events.app.subscribe` grant leaves approved). 0.47.0 gave that event a service-principal payload that does not match. Now `network.grant.changed` describes what is actually sent: `{ project_id, capability, audience, from, to }`, subject `app`. A first-party service principal's grant change is the new **`network.principal_grant.changed`** (the 0.47.0 payload under its own name). Nothing consumed the 0.47.0 shape.
+**Correction to 0.47.0.** `network.grant.changed` was already emitted by Network's developer event relay for a developer app's grant, and OpenVibe.Events acts on it (it stops an app's subscriptions when its `events.app.subscribe` grant leaves approved). 0.47.0 gave that event a service-principal payload that does not match. Now `network.grant.changed` describes what is actually sent: `{ project_id, capability, audience, from, to }`, subject `app`. A first-party service principal's grant change is the new **`network.principal_grant.changed`** (the 0.47.0 payload under its own name). Nothing consumed the 0.47.0 shape. The compat gate records this in `compatibility/corrections.json` (accepted against v0.47.0 only).
## 0.47.0 — 2026-09-25
## 0.47.0 — 2026-09-25
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "_about": "Recorded corrections: a contract released with a schema that nothing ever produced or consumed, fixed in the next release to describe what is actually sent. { id, base (the release tag the correction is compared with), fixedIn, reason }. The compat gate accepts the listed contract's changes against that one base only; every other comparison, and every later release, is checked as usual. Never for a contract anyone implemented.",
+ "corrections": [
+ {
+ "id": "network.grant.changed",
+ "base": "v0.47.0",
+ "fixedIn": "0.48.0",
+ "reason": "0.47.0 gave network.grant.changed a service-principal payload, but Network's developer relay already sent it for developer-app grants ({ project_id, capability, audience, from, to }) and OpenVibe.Events acts on that shape. Nothing produced or consumed the 0.47.0 shape; it moved to network.principal_grant.changed."
+ }
+ ]
+}
@@ -43,10 +43,13 @@ function compare(id, a, b, at) {
@@ -43,10 +43,13 @@ function compare(id, a, b, at) {
const oldCat = readAt(base, 'contracts/catalog.json');
const oldCat = readAt(base, 'contracts/catalog.json');
const newCat = JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts/catalog.json'), 'utf8'));
const newCat = JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts/catalog.json'), 'utf8'));
const deprecated = new Set(JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/deprecations.json'), 'utf8')).deprecations.map(d => d.id));
const deprecated = new Set(JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/deprecations.json'), 'utf8')).deprecations.map(d => d.id));
+// A recorded correction (compatibility/corrections.json) is accepted against its one base tag only.
+const corrected = new Set(JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/corrections.json'), 'utf8')).corrections.filter(c => c.base === base).map(c => c.id));
for (const entry of (oldCat ? oldCat.contracts : [])) {
for (const entry of (oldCat ? oldCat.contracts : [])) {
const now = newCat.contracts.find(c => c.id === entry.id);
const now = newCat.contracts.find(c => c.id === entry.id);
if (!now) { if (!deprecated.has(entry.id)) problems.push(`${entry.id}: removed from the catalog without a deprecation`); continue; }
if (!now) { if (!deprecated.has(entry.id)) problems.push(`${entry.id}: removed from the catalog without a deprecation`); continue; }
if (now.schema !== entry.schema) continue; // new major file; old one must still be listed separately
if (now.schema !== entry.schema) continue; // new major file; old one must still be listed separately
+ if (corrected.has(entry.id)) { console.log(`compat vs ${base}: ${entry.id} is a recorded correction (compatibility/corrections.json)`); continue; }
compare(entry.id, readAt(base, `contracts/${entry.schema}`), JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', entry.schema), 'utf8')), '');
compare(entry.id, readAt(base, `contracts/${entry.schema}`), JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', entry.schema), 'utf8')), '');
}
}
if (problems.length) { console.error(`compat vs ${base}: ${problems.length} breaking change(s)\n ${problems.join('\n ')}`); process.exit(1); }
if (problems.length) { console.error(`compat vs ${base}: ${problems.length} breaking change(s)\n ${problems.join('\n ')}`); process.exit(1); }