openvibers/openvibe.shared Commits
4.112.0: x-unit seconds in config schemas (#290)
Changes
@@@ -1,5 +1,12 @@# Changelog++## 4.112.0 — 2026-11-05++++- **`openvibe-shared/config`: `x-unit: "seconds"` is an owner-setting unit.** An integer property may now be++ annotated `seconds` (alongside `nano_usd`, `ms` and `bps`), and `summary()` carries the annotation unchanged so a++ settings form can show the input in seconds with the label "s". An unknown unit is still refused when the store is++ created, naming the unit.++## 4.111.1 — 2026-11-05- **The tab icon is the Pair on every site.** `theme-loader.js` repaints the favicon on each theme apply@@@ -2,7 +2,7 @@> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.--**Status:** alpha, v4.111.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.112.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployedconsumer installs a tagged release and none keeps a vendored copy([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` namesthe tag it pins.@@@ -344,7 +344,7 @@ settings.sealed; settings.kek; // false / null without a KEKmarker with its four-character `hint`, `set_at`, `set_by`, `bound_to`) and `schema`, the JSONSchema to draw the form from; a secret property's `default`, `examples`, `enum` and `const` areremoved from that copy. The annotations are `title`, `description`, `x-group`, `x-env`, `x-danger`,-- `x-bind` and `x-unit` (`nano_usd`, `ms`, `bps`).++ `x-bind` and `x-unit` (`nano_usd`, `ms`, `bps`, `seconds`).- **Hooks.** `onChange({ namespace, keys, actor, revision })` runs after every successful activation(never for a proposal or the seed), and a throwing hook is logged and never refuses the revision.`requireFresh(req, { namespace, keys })`, an `adminRoutes` option, runs before a secret write, a@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.111.1",++ "version": "4.112.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)","deployed": false,"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",@@@ -27,7 +27,8 @@"a CONFIRMATION_REQUESTED notification type: an agent owner is told when a sensitive-capability confirmation awaits them","the Dock pops out into a window of its own (Document Picture-in-Picture): the same panel, the same tabs, the page's pill saying where the Dock went","the Dock accepts a refreshing token (string, sync or async) and an optional read-only scope set: the extension hides approve/decline and the chat composer while keeping reads",-- "vibes-pay.js: OpenVibe.vibes.pay(payUrl) opens the Vibes Pay sheet (popup on a computer, full page on a phone), resolves { status } from an origin-checked postMessage and binds <button data-ov-vibes-pay> in the Vibes accent, both themes (D12)"++ "vibes-pay.js: OpenVibe.vibes.pay(payUrl) opens the Vibes Pay sheet (popup on a computer, full page on a phone), resolves { status } from an origin-checked postMessage and binds <button data-ov-vibes-pay> in the Vibes accent, both themes (D12)",++ "openvibe-shared/config accepts x-unit seconds for an integer owner setting, so an owner form can label the input in seconds"],-- "updated": "2026-11-04"++ "updated": "2026-11-05"}@@@ -265,7 +265,7 @@ const KEYWORDS = new Set(['type', 'enum', 'const', 'properties', 'required', 'ad'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf']);const ANNOTATIONS = new Set(['$schema', '$id', '$comment', 'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'format']);const TYPES = ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'];--const UNITS = new Set(['nano_usd', 'ms', 'bps']);++const UNITS = new Set(['nano_usd', 'ms', 'bps', 'seconds']);const ENV_NAME = /^[A-Z][A-Z0-9_]{0,127}$/;const patterns = new WeakMap();@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.111.1",++ "version": "4.112.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.111.1",++ "version": "4.112.0","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.111.1",++ "version": "4.112.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -363,7 +363,10 @@ const noSecret = (text, values, what) => {assert.strictEqual(bindLeakSummary.secrets.bound_key.bound_to, null);const enumSecret = await createConfigStore({ db, service: 'live', namespace: 'live.enumsecret', schema: { type: 'object', additionalProperties: false, properties: { api_key: { type: 'string', enum: ['ENUM-MADE-UP-SECRET-7'] } } }, classify, defaults: {}, keks: keks1, log });await assert.rejects(enumSecret.setSecret('api_key', 'another value', { actor: USER }), (e) => e.code === 'config.invalid' && !JSON.stringify(e).includes('ENUM-MADE-UP-SECRET-7'));-- await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badunit', schema: { type: 'object', properties: { x: { type: 'integer', 'x-unit': 'seconds' } } }, keks: keks1, log }), /x-unit/);++ // `seconds` is an owner-setting unit (an integer the form shows in seconds, labelled "s"); anything else is refused.++ const seconds = await createConfigStore({ db, service: 'live', namespace: 'live.secondsunit', schema: { type: 'object', properties: { x: { type: 'integer', 'x-unit': 'seconds' } } }, keks: keks1, log });++ assert.strictEqual((await seconds.summary()).schema.properties.x['x-unit'], 'seconds');++ await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badunit', schema: { type: 'object', properties: { x: { type: 'integer', 'x-unit': 'fortnights' } } }, keks: keks1, log }), /x-unit/);await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badenv', schema: { type: 'object', properties: { x: { type: 'string', 'x-env': 'lower-case' } } }, keks: keks1, log }), /x-env/);await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.baddanger', schema: { type: 'object', properties: { x: { type: 'boolean', 'x-danger': 'yes' } } }, keks: keks1, log }), /x-danger/);
@@ -1,5 +1,12 @@
@@ -1,5 +1,12 @@
# Changelog
# Changelog
+## 4.112.0 — 2026-11-05
+
+- **`openvibe-shared/config`: `x-unit: "seconds"` is an owner-setting unit.** An integer property may now be
+ annotated `seconds` (alongside `nano_usd`, `ms` and `bps`), and `summary()` carries the annotation unchanged so a
+ settings form can show the input in seconds with the label "s". An unknown unit is still refused when the store is
+ created, naming the unit.
+
## 4.111.1 — 2026-11-05
## 4.111.1 — 2026-11-05
- **The tab icon is the Pair on every site.** `theme-loader.js` repaints the favicon on each theme apply
- **The tab icon is the Pair on every site.** `theme-loader.js` repaints the favicon on each theme apply
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
-**Status:** alpha, v4.111.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.112.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -344,7 +344,7 @@ settings.sealed; settings.kek; // false / null without a KEK
@@ -344,7 +344,7 @@ settings.sealed; settings.kek; // false / null without a KEK
marker with its four-character `hint`, `set_at`, `set_by`, `bound_to`) and `schema`, the JSON
marker with its four-character `hint`, `set_at`, `set_by`, `bound_to`) and `schema`, the JSON
Schema to draw the form from; a secret property's `default`, `examples`, `enum` and `const` are
Schema to draw the form from; a secret property's `default`, `examples`, `enum` and `const` are
removed from that copy. The annotations are `title`, `description`, `x-group`, `x-env`, `x-danger`,
removed from that copy. The annotations are `title`, `description`, `x-group`, `x-env`, `x-danger`,
- `x-bind` and `x-unit` (`nano_usd`, `ms`, `bps`).
+ `x-bind` and `x-unit` (`nano_usd`, `ms`, `bps`, `seconds`).
- **Hooks.** `onChange({ namespace, keys, actor, revision })` runs after every successful activation
- **Hooks.** `onChange({ namespace, keys, actor, revision })` runs after every successful activation
(never for a proposal or the seed), and a throwing hook is logged and never refuses the revision.
(never for a proposal or the seed), and a throwing hook is logged and never refuses the revision.
`requireFresh(req, { namespace, keys })`, an `adminRoutes` option, runs before a secret write, a
`requireFresh(req, { namespace, keys })`, an `adminRoutes` option, runs before a secret write, a
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.111.1",
+ "version": "4.112.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -27,7 +27,8 @@
@@ -27,7 +27,8 @@
"a CONFIRMATION_REQUESTED notification type: an agent owner is told when a sensitive-capability confirmation awaits them",
"a CONFIRMATION_REQUESTED notification type: an agent owner is told when a sensitive-capability confirmation awaits them",
"the Dock pops out into a window of its own (Document Picture-in-Picture): the same panel, the same tabs, the page's pill saying where the Dock went",
"the Dock pops out into a window of its own (Document Picture-in-Picture): the same panel, the same tabs, the page's pill saying where the Dock went",
"the Dock accepts a refreshing token (string, sync or async) and an optional read-only scope set: the extension hides approve/decline and the chat composer while keeping reads",
"the Dock accepts a refreshing token (string, sync or async) and an optional read-only scope set: the extension hides approve/decline and the chat composer while keeping reads",
- "vibes-pay.js: OpenVibe.vibes.pay(payUrl) opens the Vibes Pay sheet (popup on a computer, full page on a phone), resolves { status } from an origin-checked postMessage and binds <button data-ov-vibes-pay> in the Vibes accent, both themes (D12)"
+ "vibes-pay.js: OpenVibe.vibes.pay(payUrl) opens the Vibes Pay sheet (popup on a computer, full page on a phone), resolves { status } from an origin-checked postMessage and binds <button data-ov-vibes-pay> in the Vibes accent, both themes (D12)",
+ "openvibe-shared/config accepts x-unit seconds for an integer owner setting, so an owner form can label the input in seconds"
],
],
- "updated": "2026-11-04"
+ "updated": "2026-11-05"
}
}
@@ -265,7 +265,7 @@ const KEYWORDS = new Set(['type', 'enum', 'const', 'properties', 'required', 'ad
@@ -265,7 +265,7 @@ const KEYWORDS = new Set(['type', 'enum', 'const', 'properties', 'required', 'ad
'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf']);
'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf']);
const ANNOTATIONS = new Set(['$schema', '$id', '$comment', 'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'format']);
const ANNOTATIONS = new Set(['$schema', '$id', '$comment', 'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'format']);
const TYPES = ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'];
const TYPES = ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'];
-const UNITS = new Set(['nano_usd', 'ms', 'bps']);
+const UNITS = new Set(['nano_usd', 'ms', 'bps', 'seconds']);
const ENV_NAME = /^[A-Z][A-Z0-9_]{0,127}$/;
const ENV_NAME = /^[A-Z][A-Z0-9_]{0,127}$/;
const patterns = new WeakMap();
const patterns = new WeakMap();
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.111.1",
+ "version": "4.112.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.111.1",
+ "version": "4.112.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.111.1",
+ "version": "4.112.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -363,7 +363,10 @@ const noSecret = (text, values, what) => {
@@ -363,7 +363,10 @@ const noSecret = (text, values, what) => {
assert.strictEqual(bindLeakSummary.secrets.bound_key.bound_to, null);
assert.strictEqual(bindLeakSummary.secrets.bound_key.bound_to, null);
const enumSecret = await createConfigStore({ db, service: 'live', namespace: 'live.enumsecret', schema: { type: 'object', additionalProperties: false, properties: { api_key: { type: 'string', enum: ['ENUM-MADE-UP-SECRET-7'] } } }, classify, defaults: {}, keks: keks1, log });
const enumSecret = await createConfigStore({ db, service: 'live', namespace: 'live.enumsecret', schema: { type: 'object', additionalProperties: false, properties: { api_key: { type: 'string', enum: ['ENUM-MADE-UP-SECRET-7'] } } }, classify, defaults: {}, keks: keks1, log });
await assert.rejects(enumSecret.setSecret('api_key', 'another value', { actor: USER }), (e) => e.code === 'config.invalid' && !JSON.stringify(e).includes('ENUM-MADE-UP-SECRET-7'));
await assert.rejects(enumSecret.setSecret('api_key', 'another value', { actor: USER }), (e) => e.code === 'config.invalid' && !JSON.stringify(e).includes('ENUM-MADE-UP-SECRET-7'));
- await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badunit', schema: { type: 'object', properties: { x: { type: 'integer', 'x-unit': 'seconds' } } }, keks: keks1, log }), /x-unit/);
+ // `seconds` is an owner-setting unit (an integer the form shows in seconds, labelled "s"); anything else is refused.
+ const seconds = await createConfigStore({ db, service: 'live', namespace: 'live.secondsunit', schema: { type: 'object', properties: { x: { type: 'integer', 'x-unit': 'seconds' } } }, keks: keks1, log });
+ assert.strictEqual((await seconds.summary()).schema.properties.x['x-unit'], 'seconds');
+ await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badunit', schema: { type: 'object', properties: { x: { type: 'integer', 'x-unit': 'fortnights' } } }, keks: keks1, log }), /x-unit/);
await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badenv', schema: { type: 'object', properties: { x: { type: 'string', 'x-env': 'lower-case' } } }, keks: keks1, log }), /x-env/);
await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.badenv', schema: { type: 'object', properties: { x: { type: 'string', 'x-env': 'lower-case' } } }, keks: keks1, log }), /x-env/);
await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.baddanger', schema: { type: 'object', properties: { x: { type: 'boolean', 'x-danger': 'yes' } } }, keks: keks1, log }), /x-danger/);
await assert.rejects(createConfigStore({ db, service: 'live', namespace: 'live.baddanger', schema: { type: 'object', properties: { x: { type: 'boolean', 'x-danger': 'yes' } } }, keks: keks1, log }), /x-danger/);