openvibers/openvibe.contracts Commits

Contracts: docs-currency skips placeholder services with no repository; STATUS.json current (v0.126.0) (#77)

OP OpenVibers committed 10 Oct 2026 · 5304ca4ce3 · 4 files, +95 −9

Two documentation-currency fixes:

- `scripts/docs-currency.js`: a service whose manifest status is `placeholder` and that has no checkout (zone today) is reported as skipped, not as a gap, and the summary counts it separately ("39/43 current, 1 placeholder skipped"). A placeholder with a checkout is still checked. Covered by the new `test/docs-currency.test.js`.
- `STATUS.json`: `updated` 2026-10-10, `contracts` v0.126.0 (it still said v0.106.0), and `features` lists what shipped since v0.106.0 (Inventory, Workshop, Food/Help/Work/Quest/Rent, News/Reviews/Trade/Coupons/OpenRestream live, ADR-055 proposed…).

No schema or manifest change, so no new tag is needed. Drafted by a DeepSeek job and reviewed here.

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex b9da5ace..34871d74 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -9,7 +9,7 @@
"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1",
"deployed": false,
"deployment": "none of its own: a library and artifact repository published as release tags; services pin a tag and deploy it with their own releases",
-- "contracts": "openvibe-contracts v0.106.0 (this package; the newest tag)",
++ "contracts": "openvibe-contracts v0.126.0 (this package; the newest tag)",
"features": [
"589 JSON Schemas with valid and invalid fixtures",
"31 service manifests with lifecycle declarations",
@@@ -26,7 +26,20 @@
"platform.task@1 (planned, owner network) and ADR-044: one task at OpenVibe.Actor's router with a mode, a per-task and per-day budget, result, cost and explanation, SSE progress, cancel and webhooks",
"watch.watch@1 and the other Watch contracts (plan T18, planned), three watch events, four capabilities and the placeholder service manifest: user-defined persistent observations over pages, feeds, APIs, events and devices",
"chat.site-daily-result@1, chat.first-chat-result@1 and chat.sound-result@1: Chat's three new internal reads (kind site-daily for Live's home series, the welcome check, and the sound a !command plays), with chat.stats-request@1 now accepting kind site-daily and the two route lists extended",
-- "network.resource.read (active, ADR-048): OpenVibe.Network's resource index, GET /api/v1/resources (common.resource-list-result@1) and GET /api/v1/resources/:ovrn (common.resource-summary@1)"
++ "network.resource.read (active, ADR-048): OpenVibe.Network's resource index, GET /api/v1/resources (common.resource-list-result@1) and GET /api/v1/resources/:ovrn (common.resource-summary@1)",
++ "OpenVibe.Bot is a site (navigation and the network home list it); bot.robot-profile's mapping.driver gains relay (plan T15 R9)",
++ "resource-index capabilities for media, events, host and codes are active (plan T13 step 8) and Host is live; Run and Services register, with partial resource pages, Bot button/point commands and ai.app.run",
++ "OpenVibe.Watch runs (alpha, capabilities active) and goes live with its public site (plan T18); OpenVibe.Space and AI's public home launch; Events' public origin is openvibe.events (plan T7); Community's forum capabilities retire",
++ "the developer portal moves to OpenVibe.Services and Codes becomes the harness; developer links point at OpenVibe.Services",
++ "OpenVibe.Actor becomes a service: the task router, its capabilities and schemas (plan T17, ADR-044)",
++ "OpenVibe.Food, OpenVibe.Help and OpenVibe.Work become services (plan T19); OpenVibe.Quest and OpenVibe.Rent become services",
++ "the forum returns to Community; Deals goes live, with product copy for the new sites",
++ "OpenVibe.MediaHub goes live (openvibe.download as a private drive); services declare the account events they consume (ADR-033, SDK 0.37.0)",
++ "ADR-054 — the OpenVibe inventory authority (kinds, definitions, instances, equipped and the ledger), its planned service, capabilities, schemas and events (plan T21 step 1)",
++ "OpenVibe.Inventory goes live (inventory.openvibe.network, site position 30) with six capabilities active (ADR-054, plan T21 step 2); a definition may name the services or apps that may grant it",
++ "the Workshop v1 (ADR-054 section 6): Workshop kinds, network.badge, staff review (inventory.review-request@1, inventory.definition.review active) and media_id on equipped slots",
++ "News, Reviews, Trade, Coupons and OpenRestream go live on their own domains (plan T11); the product at openre.stream is OpenRestream, with OpenVibe Live on by default (live.openre.slot.bind)",
++ "ADR-055 (proposed): money and the inventory — primary sales first, gifting next; trading and a market stay closed, and chance is never paid for"
],
-- "updated": "2026-10-07"
++ "updated": "2026-10-10"
}
ddiff --git a/scripts/docs-currency.js b/scripts/docs-currency.js
iindex b054f97b..4c3fa2c2 100644
---- a/scripts/docs-currency.js
++++ b/scripts/docs-currency.js
@@@ -9,7 +9,9 @@
* node scripts/docs-currency.js <root> --only search,live
*
* The services are manifests/services/*.json (their `repository`, e.g. OpenVibers/OpenVibe.Search, is looked up as
-- * <root>/OpenVibe.Search). For each checkout:
++ * <root>/OpenVibe.Search). A manifest with status "placeholder" is one the roadmap has named but not built, so an
++ * absent checkout is skipped (not a gap); a placeholder that does have a checkout is checked like any other. For
++ * each checkout:
*
* README.md has a level-2 heading for each of: Purpose, Owns, Does not own, Depends on, Capabilities or Grants,
* Acceptance (or Tests), Security, Deploy (or Deployment, Running it in production). "What works" is read from
@@@ -60,7 +62,13 @@ function checkRepo(root, manifest) {
const name = String(manifest.repository || '').split('/').pop();
const dir = path.join(root, name);
const gaps = [];
-- if (!name || !fs.existsSync(dir)) return { id: manifest.id, repository: manifest.repository, missing: true, gaps: ['no checkout under the root'] };
++ if (!name || !fs.existsSync(dir)) {
++ // A placeholder service (status "placeholder": named by the roadmap, not built) has no repository to check
++ // yet; report it as skipped rather than a gap. A placeholder that does have a checkout falls through and is
++ // checked normally.
++ if (manifest.status === 'placeholder') return { id: manifest.id, repository: manifest.repository, skipped: 'placeholder', gaps: [] };
++ return { id: manifest.id, repository: manifest.repository, missing: true, gaps: ['no checkout under the root'] };
++ }
const readmePath = path.join(dir, 'README.md');
if (!fs.existsSync(readmePath)) gaps.push('README.md missing');
else {
@@@ -86,6 +94,14 @@ function checkRepo(root, manifest) {
return { id: manifest.id, repository: manifest.repository, gaps };
}
++/** The summary line: skipped placeholders are counted apart from the checkouts that were actually checked. */
++function summarize(results) {
++ const skipped = results.filter((r) => r.skipped).length;
++ const checked = results.length - skipped;
++ const bad = results.filter((r) => r.gaps.length).length;
++ return `${checked - bad}/${checked} current${skipped ? `, ${skipped} placeholder skipped` : ''}`;
++}
++
function main(argv) {
const root = argv.find((a) => !a.startsWith('--'));
if (!root) { console.error('usage: docs-currency.js <root with the checkouts> [--json] [--only id,id]'); process.exit(2); }
@@@ -97,12 +113,14 @@ function main(argv) {
const results = manifests.map((m) => checkRepo(path.resolve(root), m));
if (argv.includes('--json')) console.log(JSON.stringify(results, null, 2));
else {
-- for (const r of results) console.log(`${r.gaps.length ? '✗' : '✓'} ${r.id.padEnd(12)} ${r.gaps.length ? r.gaps.join('; ') : 'current'}`);
-- const bad = results.filter((r) => r.gaps.length).length;
-- console.log(`${results.length - bad}/${results.length} current`);
++ for (const r of results) {
++ if (r.skipped) console.log(`– ${r.id.padEnd(12)} placeholder, no repository yet`);
++ else console.log(`${r.gaps.length ? '✗' : '✓'} ${r.id.padEnd(12)} ${r.gaps.length ? r.gaps.join('; ') : 'current'}`);
++ }
++ console.log(summarize(results));
}
process.exit(results.some((r) => r.gaps.length) ? 1 : 0);
}
if (require.main === module) main(process.argv.slice(2));
--module.exports = { checkRepo, headings, contractsPin, REQUIRED, STATUS_FIELDS };
++module.exports = { checkRepo, headings, contractsPin, summarize, REQUIRED, STATUS_FIELDS };
ddiff --git a/test/docs-currency.test.js b/test/docs-currency.test.js
nnew file mode 100644
iindex 00000000..3943e5e5
---- /dev/null
++++ b/test/docs-currency.test.js
@@@ -0,0 +1,53 @@
++'use strict';
++// Documentation currency (roadmap WS-U task 3): a service manifest with status "placeholder" is one the roadmap
++// has named but not built, so an absent repository is not a gap — it is skipped. A placeholder that does have a
++// checkout is checked like every other service, and the summary counts skipped services apart from the checked ones.
++const assert = require('assert');
++const fs = require('fs');
++const os = require('os');
++const path = require('path');
++const { checkRepo, summarize } = require('../scripts/docs-currency.js');
++
++let checks = 0;
++const ok = (cond, msg) => { assert.ok(cond, msg); checks++; };
++
++const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ovc-docs-currency-'));
++const placeholder = { id: 'parked', repository: 'OpenVibers/OpenVibe.Parked', status: 'placeholder' };
++try {
++ // A placeholder with no checkout is skipped, not failed.
++ const skipped = checkRepo(root, placeholder);
++ ok(skipped.skipped, 'a placeholder with no checkout is skipped');
++ ok(skipped.gaps.length === 0, 'a skipped placeholder is not a gap');
++ ok(/placeholder/.test(skipped.skipped), 'the skip says why');
++
++ // A placeholder that does have a checkout is checked normally: a current checkout is current...
++ const dir = path.join(root, 'OpenVibe.Parked');
++ fs.mkdirSync(dir);
++ fs.writeFileSync(path.join(dir, 'README.md'), [
++ '# OpenVibe.Parked', '',
++ '## Purpose', 'x', '## Owns', 'x', '## Does not own', 'x', '## Depends on', 'x',
++ '## Capabilities', 'x', '## Acceptance', 'x', '## Security', 'x', '## Deploy', 'x', '',
++ ].join('\n'));
++ fs.writeFileSync(path.join(dir, 'STATUS.json'), JSON.stringify({
++ repository: 'OpenVibers/OpenVibe.Parked', stage: 'alpha', deployed: false,
++ contracts: 'openvibe-contracts v9.9.9', features: [], updated: '2099-01-01',
++ }));
++ const current = checkRepo(root, placeholder);
++ ok(!current.skipped && current.gaps.length === 0, `a placeholder with a current checkout is current: ${current.gaps.join('; ')}`);
++
++ // ...and a gap in that checkout is still reported, because it was checked and not skipped.
++ fs.rmSync(path.join(dir, 'README.md'));
++ const broken = checkRepo(root, placeholder);
++ ok(!broken.skipped && broken.gaps.some((g) => /README\.md missing/.test(g)), 'a placeholder with a checkout reports its gaps');
++
++ // A service with no checkout and no placeholder status is still a gap.
++ const missing = checkRepo(root, { id: 'built', repository: 'OpenVibers/OpenVibe.Built', status: 'alpha' });
++ ok(!missing.skipped && missing.gaps.some((g) => /no checkout/.test(g)), 'a non-placeholder with no checkout is still a gap');
++
++ // The summary counts skipped services separately and only the checked ones in the ratio.
++ ok(summarize([{ gaps: [] }, { gaps: ['x'] }, { skipped: 'placeholder', gaps: [] }]) === '1/2 current, 1 placeholder skipped', 'the summary counts skipped placeholders separately');
++ ok(summarize([{ gaps: [] }, { gaps: [] }]) === '2/2 current', 'without a placeholder the summary is unchanged');
++} finally {
++ fs.rmSync(root, { recursive: true, force: true });
++}
++console.log(`docs-currency: ${checks} checks passed`);
ddiff --git a/test/run.js b/test/run.js
iindex 291a8a66..04e42cec 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -918,6 +918,8 @@ sub(path.join(__dirname, 'zones.test.js'));
sub(path.join(__dirname, 'usage-topics.test.js'));
sub(path.join(__dirname, 'billing-grace.test.js'));
sub(path.join(__dirname, 'resources.test.js'));
++// Documentation currency (roadmap WS-U task 3): a placeholder service with no repository yet is skipped, not a gap.
++sub(path.join(__dirname, 'docs-currency.test.js'));
// ── Estate (plan T1 step 3): docs/ESTATE.md and manifests/repositories are generated ──
// A checkout tree is read into one repository manifest and the document; --check fails on drift, and with