openvibers/openvibe.codes Commits
Governance drafts: code of conduct, contributing, contributor ladder, moderation policy
Changes
nnew file mode 100644@@@ -0,0 +1,47 @@++# Code of conduct++++**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.++++This code applies to everyone who takes part in OpenVibe's developer spaces: the OpenVibers repositories on GitHub (issues, pull requests, discussions, reviews), OpenVibe.Codes, and any chat or call about building on OpenVibe. The products themselves (Live, Chat, Community and the rest) have their own rules for viewers and streamers; this code is about how we work together on the platform and the apps built on it.++++## What we expect++++- Be respectful. Disagree with ideas, not with people. Assume the other person is acting in good faith until they show otherwise.++- Be clear and specific. Say what you saw, what you expected and what you propose. A short, concrete report helps more than a long complaint.++- Accept review. Code review is about the change, not about you. Reviewers explain what needs to change and why.++- Credit other people's work. Say where code, ideas or text came from, and respect their licenses.++- Keep secrets and personal data out of public places. Never post credentials, tokens, private keys or someone else's personal information in an issue, a pull request or a log.++- Report security problems privately (see [Contributing](https://openvibe.codes/policy/contributing)), not in a public issue.++++## What is not acceptable++++- Harassment, threats or intimidation, in public or in private messages.++- Insults, slurs or demeaning comments about a person or a group, including about gender, sexual orientation, disability, ethnicity, nationality, religion, age, appearance or experience level.++- Sexual content or attention that is not welcome, and any sexual content involving minors.++- Publishing someone's private information (doxxing), or threatening to.++- Deliberately disrupting discussions, reviews or releases, or repeatedly pushing a point after a maintainer has asked you to stop.++- Using OpenVibe's developer tools to attack other developers, users or services, for example publishing a release that is designed to harm the people who install it.++++## Reporting++++If someone breaks this code, or you are not sure whether something is acceptable, tell the maintainers privately at [email protected]. Include links or screenshots if you have them. You do not need to have been the target to report.++++Reports are read by the maintainers and kept confidential. Nobody who reports in good faith will be punished for it. If the report is about a maintainer, that maintainer takes no part in handling it.++++## What happens next++++Maintainers decide what to do based on what happened, its impact and whether it has happened before. The steps, from mildest to most serious, are described in the [moderation policy](https://openvibe.codes/policy/moderation):++++1. A private note explaining what was wrong.++2. A public or private warning, with a time-limited restriction if needed (for example, no comments on one issue for a week).++3. A temporary ban from the repositories and developer spaces.++4. A permanent ban.++++Threats of violence, sexual content involving minors and doxxing may lead straight to a permanent ban and, where the law requires it, a report to the authorities.++++You can ask for a decision to be reviewed; the moderation policy explains how.++++## Open questions for the owner++++- Confirm the reporting address (the draft uses [email protected]) or name another one.++- Decide whether to adopt a published code (for example the Contributor Covenant) instead of this text.nnew file mode 100644@@@ -0,0 +1,50 @@++# Contributing to OpenVibe++++**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.++++Thank you for helping. This guide covers every OpenVibers repository; a repository's own README adds anything specific to it (how to run it, its tests, its deploy rules). By taking part you agree to the [code of conduct](https://openvibe.codes/policy/code-of-conduct).++++## Where things live++++- Each service has its own repository under [github.com/OpenVibers](https://github.com/OpenVibers): OpenVibe.Network (identity, projects, apps, grants), OpenVibe.Live, OpenVibe.Media, OpenVibe.Events, OpenVibe.Chat, OpenVibe.Codes (this portal), and others.++- Interfaces between services live in OpenVibe.Contracts: schemas, capabilities, event types, service manifests and the decision record (ADRs).++- The client library for apps is OpenVibe.SDK (`openvibe-sdk`). Working examples are in OpenVibe.Examples.++++## Before you start++++- For a bug, open an issue in the repository that has it: what you did, what you expected, what happened, and the version (the service's `/release.json`, or the package version).++- For a small fix (a typo, a clear bug with an obvious fix), you can open a pull request straight away.++- For anything that changes a public interface — a contract, a capability, an event type, an API route others call — start with a proposal, as described in [proposals and decisions](https://openvibe.codes/policy/rfc). A pull request that changes an interface without an accepted proposal will be asked to open one first.++- Security problems: do not open a public issue. Write to [email protected] with the details and give us time to fix it before you publish anything.++++## Making a change++++1. Fork the repository and create a branch from `main`.++2. Use Node.js 22 (the version production runs). Install with `npm ci` and run the tests with `npm test`; they use temporary databases and in-process stand-ins, so they need no network and no real service.++3. Match the code around you. Most repositories use CommonJS (`require`), four-space indentation, single quotes and semicolons; there is no formatter, so keep diffs small and in the existing style.++4. Add or change tests with your change. A fix comes with a test that fails without it.++5. Keep each commit focused on one thing, with a message that says what changed and why.++6. Run `node --check` on the files you touched and the full `npm test`. Services that own capabilities also run `npx openvibe-contracts-check`; CI runs it too.++7. Open a pull request that explains the problem, the change, and how you tested it. Link the issue or proposal.++++## What reviewers look for++++- The change does what the pull request says, and nothing else.++- Tests cover it and pass.++- It keeps the platform's rules: services talk to each other through published contracts and scoped credentials, never shared internal keys; secrets are never stored in plain text, logged or shown twice; each service owns its own data.++- Public text is plain and accurate. It does not promise features that do not exist yet.++++A maintainer may ask for changes. When the pull request is approved and CI passes, a maintainer merges it. Deploying is a separate step that maintainers do.++++## Licensing++++Each repository's LICENSE file applies to contributions to it. The services are licensed under the AGPL-3.0; the SDK and contracts packages have their own licenses, shown on the [licensing page](https://openvibe.codes/policy/licensing). By opening a pull request you confirm that you have the right to contribute the code under that repository's license. Do not add code you copied from somewhere whose license does not allow it.++++## Growing as a contributor++++Regular contributors can become reviewers and maintainers. How that works is described in the [contributor ladder](https://openvibe.codes/policy/contributor-ladder).++++## Open questions for the owner++++- Whether to require a sign-off (Developer Certificate of Origin) or a contributor license agreement.++- Confirm the security reporting address (the draft uses [email protected]).@@@ -36,7 +36,7 @@ A place where a developer outside the network goes from an OpenVibe account to a| Docs | `/docs/*` | Contracts (field tables, fixtures as examples, raw schema), capability catalog (grantable ones highlighted), event types, SDK reference from its `.d.ts` files, ADRs. Every page states the versions it was generated from. The service registry is read live from Network, with health as Network reports it. || Playgrounds | `/projects/:project/apps/:app/playground` | Media upload (sandbox, `media.object.upload`) and Events publish (`events.app.publish`: types `app.<project_key>.*`, source `app-<app ULID>`), run **as the app** with its own token (from its secret typed for that one request, or a pasted token). Refused — with the missing grant named, and nothing requested or called — unless Network lists the grant as approved. Sandbox apps only. || Manifests | `/manifests/validate`, `/projects/:project/apps/:app/releases/new` | Validate `codes.app-manifest@1` and `mods.mod-manifest@1` with openvibe-contracts; create, publish, deprecate and revoke releases. |--| Policy | `/policy/*` | Proposal process and the decision record; compatibility and deprecation policy rendered from ADR-002 and ADR-016 as published; licensing read from package metadata; transparency (what Codes stores and does not). |++| Policy | `/policy/*` | Proposal process and the decision record; compatibility and deprecation policy rendered from ADR-002 and ADR-016 as published; licensing read from package metadata; transparency (what Codes stores and does not); governance — code of conduct ([CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)), contribution guide ([CONTRIBUTING.md](CONTRIBUTING.md)), [contributor ladder](docs/governance/contributor-ladder.md) and [moderation policy](docs/governance/moderation.md) — rendered from those files. All four are **drafts pending owner review**: marked at the top, shown with a notice, not indexed; deleting the draft line publishes one. || API | `/api/v1/*` | Public release reads (`codes.release.read`: no token needed; a presented token must hold it), manifest validation, docs versions; release management by an app with its own token (`codes.release.manage`). Guarded with openvibe-contracts `requireCapability`. RFC 9457 problems. || Machine | `/api/health`, `/api/ready`, `/release.json`, `/metrics` | Readiness is truthful (db and docs required; Network, JWKS, OAuth client and events relay reported as optional checks). `/metrics` answers loopback callers only. |@@@ -24,7 +24,7 @@"Webhook tester for signature v1 and v2 (server or in-browser): decides as a receiver requiring v2 does, shows the timestamp's age and the ±300 s window, v1 shown but never deciding; signed sample deliveries carry v1, X-OpenVibe-Timestamp and v2","Playgrounds (Media upload with media.object.upload, Events publish with events.app.publish) that run with the app's own token and refuse without the grant","App and mod manifest validation; release metadata (draft, publish, deprecate, revoke) with codes.app.* events through the SDK outbox; trust tiers per ADR-013 (unreviewed, reviewed, first-party) as staff-set metadata, older names migrated at boot",-- "Policy pages rendered from the published ADRs; licensing read from package metadata",++ "Policy pages rendered from the published ADRs; licensing read from package metadata; governance pages (code of conduct, contributing, contributor ladder, moderation policy) rendered from the repository's Markdown","Release API guarded by openvibe-contracts requireCapability (codes.release.manage, codes.release.read)","Readiness (/api/ready), /release.json and loopback-only /metrics"],@@@ -34,7 +34,7 @@"Billing sandbox, Chat bot and Realtime playgrounds; CLI and scaffolding; templates","Consent screen for third-party production apps (a Network gap)","No JSON-LD or llms.txt on the public pages",-- "Governance material (contribution guide, code of conduct, contributor ladder, moderation policy) is not written"++ "Governance material (code of conduct, contribution guide, contributor ladder, moderation policy) is written as drafts pending owner review: not in effect, not indexed"],"capabilitiesRegistered": true,"serviceManifestRegistered": true,nnew file mode 100644@@@ -0,0 +1,56 @@++# Contributor ladder++++**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.++++This describes the roles people can hold in the OpenVibers repositories, what each role may do, and how someone moves from one role to the next. Today one owner maintains every repository; this ladder is how other people can take on more over time.++++Roles are held per repository (or per group of related repositories), not across the whole organization. Someone can be a maintainer of OpenVibe.SDK and a contributor everywhere else.++++## Participant++++Anyone who opens an issue, comments, answers a question or reports a bug.++++- **Can:** open issues and discussions, comment, review pull requests (comments only).++- **Expected to:** follow the [code of conduct](https://openvibe.codes/policy/code-of-conduct).++++## Contributor++++Someone who has had at least one pull request merged.++++- **Can:** everything a participant can; be assigned issues.++- **Expected to:** follow the [contribution guide](https://openvibe.codes/policy/contributing), and respond to review on their own pull requests.++- **How to become one:** get a pull request merged.++++## Reviewer++++A contributor trusted to review other people's changes in a repository.++++- **Can:** approve pull requests (an approval counts toward merging), label and triage issues.++- **Expected to:** review within a reasonable time or say they cannot; check tests, contracts and the platform's rules (no shared internal keys, no secrets stored or logged, each service owns its data); be kind and specific.++- **How to become one:** several substantial merged pull requests in the repository over at least two months, and good reviews of others' changes. A maintainer proposes it; the maintainers of that repository agree.++++## Maintainer++++Someone responsible for a repository: its direction, its releases and its quality.++++- **Can:** merge pull requests; cut releases and tags; accept or decline proposals that affect only their repository; take part in moderation decisions.++- **Expected to:** keep `main` releasable and tests passing; keep the README and status notes honest; follow the [compatibility policy](https://openvibe.codes/policy/compatibility) (deprecate before removing, announce breaking changes); take part in proposals that touch their repository's interfaces.++- **How to become one:** sustained work as a reviewer, with good judgement about compatibility and security. Existing maintainers propose it; the owner approves.++- **Deploy access** (production servers, secrets) is separate from maintainer rights and is given only by the owner, to named people, for named services.++++## Owner++++The owner of the OpenVibers organization.++++- **Can:** everything; grant and remove roles; approve changes to governance documents (including this one); decide proposals that span several services when maintainers do not agree.++- **Expected to:** explain decisions that affect contributors, in public where possible.++++## Stepping down and inactivity++++Anyone can step down from a role at any time by saying so. A reviewer or maintainer who has not been active for six months may be moved to emeritus status after being asked; they can return by asking. Roles can also be removed under the [moderation policy](https://openvibe.codes/policy/moderation).++++## Open questions for the owner++++- Whether roles are per repository (as drafted) or organization-wide.++- The time thresholds (two months, six months).++- Who else, if anyone, holds reviewer or maintainer roles today.nnew file mode 100644@@@ -0,0 +1,48 @@++# Moderation policy++++**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.++++This policy explains how OpenVibe moderates its developer spaces: the OpenVibers repositories, OpenVibe.Codes, and the apps and releases published through it. Viewer- and streamer-facing products (Live, Chat, Community) keep their own moderation rules; this policy does not change them.++++## What can be moderated++++- **Conduct** in issues, pull requests, discussions and reviews, under the [code of conduct](https://openvibe.codes/policy/code-of-conduct).++- **Releases** published in Codes: a release that is malicious, misleading about what it does, infringes someone's rights, or breaks the platform's rules.++- **Apps and projects**: an app that abuses its grants, tries to get around them, or harms the people who use it.++- **Trust tiers** (unreviewed, reviewed, first-party): these are information only. A tier never grants or blocks anything; changing one is a note to users, not a sanction.++++## What moderators can do++++From mildest to most serious. Moderators pick the mildest step that fixes the problem.++++1. **Note.** A private message explaining the problem and what to change.++2. **Hide or lock.** Hide a comment, or lock an issue or discussion that has turned hostile.++3. **Warning.** A recorded warning, public or private.++4. **Deprecate or revoke a release.** A deprecated release still works and shows a reason; a revoked release is marked revoked with a public reason, and the revocation is announced through the `codes.app.revoked` event to apps that follow it.++5. **Revoke grants or credentials.** OpenVibe.Network staff can revoke an app's grants, change a project's allowance, revoke credentials or revoke the app. The app loses access immediately.++6. **Archive a project.** Every app in it is revoked. This cannot be undone.++7. **Ban.** A temporary or permanent ban from the repositories and from creating projects.++++## How decisions are made++++- Anyone can report a problem to [email protected]. Security problems in someone's app go to the same address.++- Moderators are the maintainers of the affected repository and OpenVibe staff; for anything beyond a note, at least one moderator other than the person who acted reviews it, unless the harm is immediate.++- **Immediate harm** (credentials leaked in public, a release that attacks people who install it, threats, sexual content involving minors, doxxing): moderators act first and explain afterwards.++- Moderators who are involved in the dispute do not take part in the decision.++- The owner of the organization has the final say.++++## Transparency++++- Revocation and deprecation reasons for releases are public on the release page, and changes to trust tiers are listed in the app's public trust history.++- Network keeps an append-only audit log of changes to projects, apps, credentials and grants; project admins can read it for their own project, and can download it with the project export.++- Reports and the identity of the people who report stay confidential.++++## Appeals++++If you think a decision was wrong, write to [email protected] within 30 days, say which decision and why. Someone who took no part in the original decision reviews it and answers in writing. A decision can be kept, reduced or reversed. A reversed revocation cannot bring a release back (revoked is final), but the app can publish a new version.++++## Open questions for the owner++++- Confirm the report and appeal address (the draft uses [email protected]).++- The appeal window (30 days).++- Whether to publish periodic moderation statistics.@@@ -6,7 +6,9 @@** Policy pages are derived, not paraphrased: the compatibility and deprecation policy renders* ADR-002 and ADR-016 exactly as openvibe-contracts publishes them; the decision index is the ADR-- * directory; licensing reads the license fields of the packages Codes runs.++ * directory; licensing reads the license fields of the packages Codes runs; the governance pages++ * (code of conduct, contributing, contributor ladder, moderation) render this repository's++ * Markdown files as they are.*/const fs = require('fs');const path = require('path');@@@ -22,11 +24,34 @@ const { EVENT_TYPES } = require('../events/outbox');const ROOT = path.join(__dirname, '..', '..');const readJson = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } };++/**++ * Governance documents: Markdown in this repository (the same files GitHub shows), rendered as++ * they are. A document whose text starts with the draft line is shown with a draft notice, kept out++ * of search engines and the sitemap; removing that line after the owner's review publishes it.++ */++const GOVERNANCE = [++ { slug: 'code-of-conduct', file: 'CODE_OF_CONDUCT.md', blurb: 'how we treat each other, and how to report a problem' },++ { slug: 'contributing', file: 'CONTRIBUTING.md', blurb: 'how to report a bug, propose a change and open a pull request' },++ { slug: 'contributor-ladder', file: 'docs/governance/contributor-ladder.md', blurb: 'participant, contributor, reviewer, maintainer, owner' },++ { slug: 'moderation', file: 'docs/governance/moderation.md', blurb: 'what is moderated, the steps, and appeals' },++];++const DRAFT_LINE = /^\*\*Draft — pending owner review\.\*\*.*$/m;++function loadGovernance() {++ return GOVERNANCE.map((g) => {++ const text = fs.readFileSync(path.join(ROOT, g.file), 'utf8');++ const title = (text.match(/^# (.+)$/m) || [null, g.slug])[1].trim();++ const draft = DRAFT_LINE.test(text.split('\n').slice(0, 5).join('\n'));++ const body = text.replace(/^# .+\n/m, '').replace(DRAFT_LINE, '');++ return { ...g, title, draft, body };++ });++}++function createPageRoutes(ctx) {const { config, docs, releases, trust, network, sso } = ctx;const r = asyncRouter();const form = express.urlencoded({ extended: false, limit: '32kb' });const PUBLIC_CACHE = 'public, max-age=300';++ const governance = loadGovernance();const page = (req, res, o, status = 200) => send(res, status, { viewer: req.viewer, config, path: req.originalUrl, ...o });r.get('/', (req, res) => {@@@ -63,9 +88,21 @@ ${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((<li><a href="/policy/rfc"><strong>Proposals and decisions</strong></a><span>how a contract, capability or event changes; the decision record</span></li><li><a href="/policy/compatibility"><strong>Compatibility and deprecation</strong></a><span>ADR-002 and ADR-016, as published</span></li><li><a href="/policy/licensing"><strong>Licensing</strong></a><span>what each package is licensed under</span></li>--<li><a href="/policy/transparency"><strong>Transparency</strong></a><span>what Codes stores, what it does not, and what works today</span></li></ul>`,++<li><a href="/policy/transparency"><strong>Transparency</strong></a><span>what Codes stores, what it does not, and what works today</span></li></ul>++<h2>Community</h2><ul class="cards">++${governance.map((g) => html`<li><a href="/policy/${g.slug}"><strong>${g.title}</strong></a><span>${g.blurb}${g.draft ? ' (draft, pending owner review)' : ''}</span></li>`)}</ul>`,}));++ for (const g of governance) {++ r.get(`/policy/${g.slug}`, (req, res) => page(req, res, {++ index: !g.draft, cache: PUBLIC_CACHE, title: g.title, crumbs: [{ label: 'Policy', href: '/policy' }, { label: g.title }],++ body: html`<h1>${g.title}</h1>++${g.draft ? notice(html`<strong>Draft — pending owner review.</strong> This is a proposal, not yet in effect. It takes effect when the owner of the OpenVibers organization approves it.`, 'warn') : ''}++<article class="prose">${raw(markdown(g.body, { headingOffset: 0 }))}</article>++<p class="muted small">Source: <a href="https://github.com/OpenVibers/OpenVibe.Codes/blob/main/${g.file}"><code>${g.file}</code></a>. Changes go through a pull request that the owner approves.</p>`,++ }));++ }++r.get('/policy/rfc', (req, res) => page(req, res, {index: true, cache: PUBLIC_CACHE, title: 'Proposals and decisions', crumbs: [{ label: 'Policy', href: '/policy' }, { label: 'Proposals' }],body: html`<h1>Proposals and decisions</h1>@@@ -232,6 +269,7 @@ ${signedIn ? releaseActions(req, rel) : ''}r.get('/sitemap.xml', (req, res) => {const urls = ['/', '/docs', '/docs/contracts', '/docs/capabilities', '/docs/events', '/docs/services', '/docs/sdk', '/oauth', '/tools/webhooks', '/manifests/validate','/policy', '/policy/rfc', '/policy/compatibility', '/policy/licensing', '/policy/transparency',++ ...governance.filter((g) => !g.draft).map((g) => `/policy/${g.slug}`),...docs.contracts.map((c) => `/docs/contracts/${c.id}`), ...docs.capabilities.map((c) => `/docs/capabilities/${c.id}`),...docs.sdk.map((m) => `/docs/sdk/${m.slug}`), ...docs.adrs.map((a) => `/docs/adr/${a.id}`)];const x = (s) => s.replace(/&/g, '&').replace(/</g, '<');@@@ -243,4 +281,4 @@ ${signedIn ? releaseActions(req, rel) : ''}const statusBadge = (s) => badge(s, s === 'published' ? 'ok' : (s === 'revoked' ? 'bad' : (s === 'deprecated' ? 'warn' : '')));--module.exports = { createPageRoutes, statusBadge };++module.exports = { createPageRoutes, statusBadge, GOVERNANCE };nnew file mode 100644@@@ -0,0 +1,62 @@++'use strict';++/**++ * Governance pages (roadmap §15.5): code of conduct, contribution guide, contributor ladder and++ * moderation policy are this repository's Markdown files, served under /policy as they are. Each is++ * a draft pending the owner's review: marked at the top of the file and on the page, kept out of++ * search engines and the sitemap. Links between them resolve.++ */++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const { GOVERNANCE } = require('../server/http/pages');++const { boot, check, done } = require('./helpers/boot');++++const ROOT = path.join(__dirname, '..');++++(async () => {++ const t = await boot();++++ await check('the four documents exist and each starts with the draft line', async () => {++ assert.deepStrictEqual(GOVERNANCE.map((g) => g.slug), ['code-of-conduct', 'contributing', 'contributor-ladder', 'moderation']);++ for (const g of GOVERNANCE) {++ const lines = fs.readFileSync(path.join(ROOT, g.file), 'utf8').split('\n');++ assert.match(lines[0], /^# \S/, `${g.file} starts with its title`);++ assert.match(lines[2], /^\*\*Draft — pending owner review\.\*\*/, `${g.file} is marked as a draft at the top`);++ }++ });++++ await check('each is served under /policy with the draft notice, rendered Markdown and its source', async () => {++ for (const g of GOVERNANCE) {++ const r = await t.get(`/policy/${g.slug}`);++ assert.strictEqual(r.status, 200, g.slug);++ assert.match(r.text, /<h1>[^<]+<\/h1>/);++ assert.match(r.text, /class="notice warn"[^>]*><strong>Draft — pending owner review\.<\/strong>/, `${g.slug} shows the draft notice`);++ assert.match(r.text, /<meta name="robots" content="noindex, nofollow">/, `${g.slug} is not indexed while a draft`);++ assert.match(r.text, /<h2>[^<]+<\/h2>/, `${g.slug} has its sections as h2`);++ assert.ok(r.text.includes(`https://github.com/OpenVibers/OpenVibe.Codes/blob/main/${g.file}`));++ assert.ok(!/<script(?![^>]*src=)/.test(r.text.split('<article class="prose">')[1].split('</article>')[0]), 'no inline script from Markdown');++ }++ });++++ await check('the policy index lists them as drafts; the sitemap leaves drafts out', async () => {++ const idx = await t.get('/policy');++ for (const g of GOVERNANCE) assert.ok(idx.text.includes(`href="/policy/${g.slug}"`), g.slug);++ assert.match(idx.text, /\(draft, pending owner review\)/);++ const map = await t.get('/sitemap.xml');++ for (const g of GOVERNANCE) assert.ok(!map.text.includes(`/policy/${g.slug}<`), `${g.slug} not in the sitemap while a draft`);++ assert.ok(map.text.includes('/policy/compatibility<'));++ });++++ await check('every openvibe.codes link in the documents resolves', async () => {++ const links = new Set();++ for (const g of GOVERNANCE) {++ const text = fs.readFileSync(path.join(ROOT, g.file), 'utf8');++ for (const m of text.matchAll(/\]\(https:\/\/openvibe\.codes(\/[^)\s#]*)\)/g)) links.add(m[1]);++ }++ assert.ok(links.size >= 5);++ for (const l of links) assert.strictEqual((await t.get(l)).status, 200, l);++ });++++ await t.close();++ done();++})();
new file mode 100644
new file mode 100644
@@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
+# Code of conduct
+
+**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.
+
+This code applies to everyone who takes part in OpenVibe's developer spaces: the OpenVibers repositories on GitHub (issues, pull requests, discussions, reviews), OpenVibe.Codes, and any chat or call about building on OpenVibe. The products themselves (Live, Chat, Community and the rest) have their own rules for viewers and streamers; this code is about how we work together on the platform and the apps built on it.
+
+## What we expect
+
+- Be respectful. Disagree with ideas, not with people. Assume the other person is acting in good faith until they show otherwise.
+- Be clear and specific. Say what you saw, what you expected and what you propose. A short, concrete report helps more than a long complaint.
+- Accept review. Code review is about the change, not about you. Reviewers explain what needs to change and why.
+- Credit other people's work. Say where code, ideas or text came from, and respect their licenses.
+- Keep secrets and personal data out of public places. Never post credentials, tokens, private keys or someone else's personal information in an issue, a pull request or a log.
+- Report security problems privately (see [Contributing](https://openvibe.codes/policy/contributing)), not in a public issue.
+
+## What is not acceptable
+
+- Harassment, threats or intimidation, in public or in private messages.
+- Insults, slurs or demeaning comments about a person or a group, including about gender, sexual orientation, disability, ethnicity, nationality, religion, age, appearance or experience level.
+- Sexual content or attention that is not welcome, and any sexual content involving minors.
+- Publishing someone's private information (doxxing), or threatening to.
+- Deliberately disrupting discussions, reviews or releases, or repeatedly pushing a point after a maintainer has asked you to stop.
+- Using OpenVibe's developer tools to attack other developers, users or services, for example publishing a release that is designed to harm the people who install it.
+
+## Reporting
+
+If someone breaks this code, or you are not sure whether something is acceptable, tell the maintainers privately at [email protected]. Include links or screenshots if you have them. You do not need to have been the target to report.
+
+Reports are read by the maintainers and kept confidential. Nobody who reports in good faith will be punished for it. If the report is about a maintainer, that maintainer takes no part in handling it.
+
+## What happens next
+
+Maintainers decide what to do based on what happened, its impact and whether it has happened before. The steps, from mildest to most serious, are described in the [moderation policy](https://openvibe.codes/policy/moderation):
+
+1. A private note explaining what was wrong.
+2. A public or private warning, with a time-limited restriction if needed (for example, no comments on one issue for a week).
+3. A temporary ban from the repositories and developer spaces.
+4. A permanent ban.
+
+Threats of violence, sexual content involving minors and doxxing may lead straight to a permanent ban and, where the law requires it, a report to the authorities.
+
+You can ask for a decision to be reviewed; the moderation policy explains how.
+
+## Open questions for the owner
+
+- Confirm the reporting address (the draft uses [email protected]) or name another one.
+- Decide whether to adopt a published code (for example the Contributor Covenant) instead of this text.
new file mode 100644
new file mode 100644
@@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
+# Contributing to OpenVibe
+
+**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.
+
+Thank you for helping. This guide covers every OpenVibers repository; a repository's own README adds anything specific to it (how to run it, its tests, its deploy rules). By taking part you agree to the [code of conduct](https://openvibe.codes/policy/code-of-conduct).
+
+## Where things live
+
+- Each service has its own repository under [github.com/OpenVibers](https://github.com/OpenVibers): OpenVibe.Network (identity, projects, apps, grants), OpenVibe.Live, OpenVibe.Media, OpenVibe.Events, OpenVibe.Chat, OpenVibe.Codes (this portal), and others.
+- Interfaces between services live in OpenVibe.Contracts: schemas, capabilities, event types, service manifests and the decision record (ADRs).
+- The client library for apps is OpenVibe.SDK (`openvibe-sdk`). Working examples are in OpenVibe.Examples.
+
+## Before you start
+
+- For a bug, open an issue in the repository that has it: what you did, what you expected, what happened, and the version (the service's `/release.json`, or the package version).
+- For a small fix (a typo, a clear bug with an obvious fix), you can open a pull request straight away.
+- For anything that changes a public interface — a contract, a capability, an event type, an API route others call — start with a proposal, as described in [proposals and decisions](https://openvibe.codes/policy/rfc). A pull request that changes an interface without an accepted proposal will be asked to open one first.
+- Security problems: do not open a public issue. Write to [email protected] with the details and give us time to fix it before you publish anything.
+
+## Making a change
+
+1. Fork the repository and create a branch from `main`.
+2. Use Node.js 22 (the version production runs). Install with `npm ci` and run the tests with `npm test`; they use temporary databases and in-process stand-ins, so they need no network and no real service.
+3. Match the code around you. Most repositories use CommonJS (`require`), four-space indentation, single quotes and semicolons; there is no formatter, so keep diffs small and in the existing style.
+4. Add or change tests with your change. A fix comes with a test that fails without it.
+5. Keep each commit focused on one thing, with a message that says what changed and why.
+6. Run `node --check` on the files you touched and the full `npm test`. Services that own capabilities also run `npx openvibe-contracts-check`; CI runs it too.
+7. Open a pull request that explains the problem, the change, and how you tested it. Link the issue or proposal.
+
+## What reviewers look for
+
+- The change does what the pull request says, and nothing else.
+- Tests cover it and pass.
+- It keeps the platform's rules: services talk to each other through published contracts and scoped credentials, never shared internal keys; secrets are never stored in plain text, logged or shown twice; each service owns its own data.
+- Public text is plain and accurate. It does not promise features that do not exist yet.
+
+A maintainer may ask for changes. When the pull request is approved and CI passes, a maintainer merges it. Deploying is a separate step that maintainers do.
+
+## Licensing
+
+Each repository's LICENSE file applies to contributions to it. The services are licensed under the AGPL-3.0; the SDK and contracts packages have their own licenses, shown on the [licensing page](https://openvibe.codes/policy/licensing). By opening a pull request you confirm that you have the right to contribute the code under that repository's license. Do not add code you copied from somewhere whose license does not allow it.
+
+## Growing as a contributor
+
+Regular contributors can become reviewers and maintainers. How that works is described in the [contributor ladder](https://openvibe.codes/policy/contributor-ladder).
+
+## Open questions for the owner
+
+- Whether to require a sign-off (Developer Certificate of Origin) or a contributor license agreement.
+- Confirm the security reporting address (the draft uses [email protected]).
@@ -36,7 +36,7 @@ A place where a developer outside the network goes from an OpenVibe account to a
@@ -36,7 +36,7 @@ A place where a developer outside the network goes from an OpenVibe account to a
| Docs | `/docs/*` | Contracts (field tables, fixtures as examples, raw schema), capability catalog (grantable ones highlighted), event types, SDK reference from its `.d.ts` files, ADRs. Every page states the versions it was generated from. The service registry is read live from Network, with health as Network reports it. |
| Docs | `/docs/*` | Contracts (field tables, fixtures as examples, raw schema), capability catalog (grantable ones highlighted), event types, SDK reference from its `.d.ts` files, ADRs. Every page states the versions it was generated from. The service registry is read live from Network, with health as Network reports it. |
| Playgrounds | `/projects/:project/apps/:app/playground` | Media upload (sandbox, `media.object.upload`) and Events publish (`events.app.publish`: types `app.<project_key>.*`, source `app-<app ULID>`), run **as the app** with its own token (from its secret typed for that one request, or a pasted token). Refused — with the missing grant named, and nothing requested or called — unless Network lists the grant as approved. Sandbox apps only. |
| Playgrounds | `/projects/:project/apps/:app/playground` | Media upload (sandbox, `media.object.upload`) and Events publish (`events.app.publish`: types `app.<project_key>.*`, source `app-<app ULID>`), run **as the app** with its own token (from its secret typed for that one request, or a pasted token). Refused — with the missing grant named, and nothing requested or called — unless Network lists the grant as approved. Sandbox apps only. |
| Manifests | `/manifests/validate`, `/projects/:project/apps/:app/releases/new` | Validate `codes.app-manifest@1` and `mods.mod-manifest@1` with openvibe-contracts; create, publish, deprecate and revoke releases. |
| Manifests | `/manifests/validate`, `/projects/:project/apps/:app/releases/new` | Validate `codes.app-manifest@1` and `mods.mod-manifest@1` with openvibe-contracts; create, publish, deprecate and revoke releases. |
-| Policy | `/policy/*` | Proposal process and the decision record; compatibility and deprecation policy rendered from ADR-002 and ADR-016 as published; licensing read from package metadata; transparency (what Codes stores and does not). |
+| Policy | `/policy/*` | Proposal process and the decision record; compatibility and deprecation policy rendered from ADR-002 and ADR-016 as published; licensing read from package metadata; transparency (what Codes stores and does not); governance — code of conduct ([CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)), contribution guide ([CONTRIBUTING.md](CONTRIBUTING.md)), [contributor ladder](docs/governance/contributor-ladder.md) and [moderation policy](docs/governance/moderation.md) — rendered from those files. All four are **drafts pending owner review**: marked at the top, shown with a notice, not indexed; deleting the draft line publishes one. |
| API | `/api/v1/*` | Public release reads (`codes.release.read`: no token needed; a presented token must hold it), manifest validation, docs versions; release management by an app with its own token (`codes.release.manage`). Guarded with openvibe-contracts `requireCapability`. RFC 9457 problems. |
| API | `/api/v1/*` | Public release reads (`codes.release.read`: no token needed; a presented token must hold it), manifest validation, docs versions; release management by an app with its own token (`codes.release.manage`). Guarded with openvibe-contracts `requireCapability`. RFC 9457 problems. |
| Machine | `/api/health`, `/api/ready`, `/release.json`, `/metrics` | Readiness is truthful (db and docs required; Network, JWKS, OAuth client and events relay reported as optional checks). `/metrics` answers loopback callers only. |
| Machine | `/api/health`, `/api/ready`, `/release.json`, `/metrics` | Readiness is truthful (db and docs required; Network, JWKS, OAuth client and events relay reported as optional checks). `/metrics` answers loopback callers only. |
@@ -24,7 +24,7 @@
@@ -24,7 +24,7 @@
"Webhook tester for signature v1 and v2 (server or in-browser): decides as a receiver requiring v2 does, shows the timestamp's age and the ±300 s window, v1 shown but never deciding; signed sample deliveries carry v1, X-OpenVibe-Timestamp and v2",
"Webhook tester for signature v1 and v2 (server or in-browser): decides as a receiver requiring v2 does, shows the timestamp's age and the ±300 s window, v1 shown but never deciding; signed sample deliveries carry v1, X-OpenVibe-Timestamp and v2",
"Playgrounds (Media upload with media.object.upload, Events publish with events.app.publish) that run with the app's own token and refuse without the grant",
"Playgrounds (Media upload with media.object.upload, Events publish with events.app.publish) that run with the app's own token and refuse without the grant",
"App and mod manifest validation; release metadata (draft, publish, deprecate, revoke) with codes.app.* events through the SDK outbox; trust tiers per ADR-013 (unreviewed, reviewed, first-party) as staff-set metadata, older names migrated at boot",
"App and mod manifest validation; release metadata (draft, publish, deprecate, revoke) with codes.app.* events through the SDK outbox; trust tiers per ADR-013 (unreviewed, reviewed, first-party) as staff-set metadata, older names migrated at boot",
- "Policy pages rendered from the published ADRs; licensing read from package metadata",
+ "Policy pages rendered from the published ADRs; licensing read from package metadata; governance pages (code of conduct, contributing, contributor ladder, moderation policy) rendered from the repository's Markdown",
"Release API guarded by openvibe-contracts requireCapability (codes.release.manage, codes.release.read)",
"Release API guarded by openvibe-contracts requireCapability (codes.release.manage, codes.release.read)",
"Readiness (/api/ready), /release.json and loopback-only /metrics"
"Readiness (/api/ready), /release.json and loopback-only /metrics"
],
],
@@ -34,7 +34,7 @@
@@ -34,7 +34,7 @@
"Billing sandbox, Chat bot and Realtime playgrounds; CLI and scaffolding; templates",
"Billing sandbox, Chat bot and Realtime playgrounds; CLI and scaffolding; templates",
"Consent screen for third-party production apps (a Network gap)",
"Consent screen for third-party production apps (a Network gap)",
"No JSON-LD or llms.txt on the public pages",
"No JSON-LD or llms.txt on the public pages",
- "Governance material (contribution guide, code of conduct, contributor ladder, moderation policy) is not written"
+ "Governance material (code of conduct, contribution guide, contributor ladder, moderation policy) is written as drafts pending owner review: not in effect, not indexed"
],
],
"capabilitiesRegistered": true,
"capabilitiesRegistered": true,
"serviceManifestRegistered": true,
"serviceManifestRegistered": true,
new file mode 100644
new file mode 100644
@@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
+# Contributor ladder
+
+**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.
+
+This describes the roles people can hold in the OpenVibers repositories, what each role may do, and how someone moves from one role to the next. Today one owner maintains every repository; this ladder is how other people can take on more over time.
+
+Roles are held per repository (or per group of related repositories), not across the whole organization. Someone can be a maintainer of OpenVibe.SDK and a contributor everywhere else.
+
+## Participant
+
+Anyone who opens an issue, comments, answers a question or reports a bug.
+
+- **Can:** open issues and discussions, comment, review pull requests (comments only).
+- **Expected to:** follow the [code of conduct](https://openvibe.codes/policy/code-of-conduct).
+
+## Contributor
+
+Someone who has had at least one pull request merged.
+
+- **Can:** everything a participant can; be assigned issues.
+- **Expected to:** follow the [contribution guide](https://openvibe.codes/policy/contributing), and respond to review on their own pull requests.
+- **How to become one:** get a pull request merged.
+
+## Reviewer
+
+A contributor trusted to review other people's changes in a repository.
+
+- **Can:** approve pull requests (an approval counts toward merging), label and triage issues.
+- **Expected to:** review within a reasonable time or say they cannot; check tests, contracts and the platform's rules (no shared internal keys, no secrets stored or logged, each service owns its data); be kind and specific.
+- **How to become one:** several substantial merged pull requests in the repository over at least two months, and good reviews of others' changes. A maintainer proposes it; the maintainers of that repository agree.
+
+## Maintainer
+
+Someone responsible for a repository: its direction, its releases and its quality.
+
+- **Can:** merge pull requests; cut releases and tags; accept or decline proposals that affect only their repository; take part in moderation decisions.
+- **Expected to:** keep `main` releasable and tests passing; keep the README and status notes honest; follow the [compatibility policy](https://openvibe.codes/policy/compatibility) (deprecate before removing, announce breaking changes); take part in proposals that touch their repository's interfaces.
+- **How to become one:** sustained work as a reviewer, with good judgement about compatibility and security. Existing maintainers propose it; the owner approves.
+- **Deploy access** (production servers, secrets) is separate from maintainer rights and is given only by the owner, to named people, for named services.
+
+## Owner
+
+The owner of the OpenVibers organization.
+
+- **Can:** everything; grant and remove roles; approve changes to governance documents (including this one); decide proposals that span several services when maintainers do not agree.
+- **Expected to:** explain decisions that affect contributors, in public where possible.
+
+## Stepping down and inactivity
+
+Anyone can step down from a role at any time by saying so. A reviewer or maintainer who has not been active for six months may be moved to emeritus status after being asked; they can return by asking. Roles can also be removed under the [moderation policy](https://openvibe.codes/policy/moderation).
+
+## Open questions for the owner
+
+- Whether roles are per repository (as drafted) or organization-wide.
+- The time thresholds (two months, six months).
+- Who else, if anyone, holds reviewer or maintainer roles today.
new file mode 100644
new file mode 100644
@@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
+# Moderation policy
+
+**Draft — pending owner review.** This text is a proposal. It takes effect only when the owner of the OpenVibers organization approves it and removes this line.
+
+This policy explains how OpenVibe moderates its developer spaces: the OpenVibers repositories, OpenVibe.Codes, and the apps and releases published through it. Viewer- and streamer-facing products (Live, Chat, Community) keep their own moderation rules; this policy does not change them.
+
+## What can be moderated
+
+- **Conduct** in issues, pull requests, discussions and reviews, under the [code of conduct](https://openvibe.codes/policy/code-of-conduct).
+- **Releases** published in Codes: a release that is malicious, misleading about what it does, infringes someone's rights, or breaks the platform's rules.
+- **Apps and projects**: an app that abuses its grants, tries to get around them, or harms the people who use it.
+- **Trust tiers** (unreviewed, reviewed, first-party): these are information only. A tier never grants or blocks anything; changing one is a note to users, not a sanction.
+
+## What moderators can do
+
+From mildest to most serious. Moderators pick the mildest step that fixes the problem.
+
+1. **Note.** A private message explaining the problem and what to change.
+2. **Hide or lock.** Hide a comment, or lock an issue or discussion that has turned hostile.
+3. **Warning.** A recorded warning, public or private.
+4. **Deprecate or revoke a release.** A deprecated release still works and shows a reason; a revoked release is marked revoked with a public reason, and the revocation is announced through the `codes.app.revoked` event to apps that follow it.
+5. **Revoke grants or credentials.** OpenVibe.Network staff can revoke an app's grants, change a project's allowance, revoke credentials or revoke the app. The app loses access immediately.
+6. **Archive a project.** Every app in it is revoked. This cannot be undone.
+7. **Ban.** A temporary or permanent ban from the repositories and from creating projects.
+
+## How decisions are made
+
+- Anyone can report a problem to [email protected]. Security problems in someone's app go to the same address.
+- Moderators are the maintainers of the affected repository and OpenVibe staff; for anything beyond a note, at least one moderator other than the person who acted reviews it, unless the harm is immediate.
+- **Immediate harm** (credentials leaked in public, a release that attacks people who install it, threats, sexual content involving minors, doxxing): moderators act first and explain afterwards.
+- Moderators who are involved in the dispute do not take part in the decision.
+- The owner of the organization has the final say.
+
+## Transparency
+
+- Revocation and deprecation reasons for releases are public on the release page, and changes to trust tiers are listed in the app's public trust history.
+- Network keeps an append-only audit log of changes to projects, apps, credentials and grants; project admins can read it for their own project, and can download it with the project export.
+- Reports and the identity of the people who report stay confidential.
+
+## Appeals
+
+If you think a decision was wrong, write to [email protected] within 30 days, say which decision and why. Someone who took no part in the original decision reviews it and answers in writing. A decision can be kept, reduced or reversed. A reversed revocation cannot bring a release back (revoked is final), but the app can publish a new version.
+
+## Open questions for the owner
+
+- Confirm the report and appeal address (the draft uses [email protected]).
+- The appeal window (30 days).
+- Whether to publish periodic moderation statistics.
@@ -6,7 +6,9 @@
@@ -6,7 +6,9 @@
*
*
* Policy pages are derived, not paraphrased: the compatibility and deprecation policy renders
* Policy pages are derived, not paraphrased: the compatibility and deprecation policy renders
* ADR-002 and ADR-016 exactly as openvibe-contracts publishes them; the decision index is the ADR
* ADR-002 and ADR-016 exactly as openvibe-contracts publishes them; the decision index is the ADR
- * directory; licensing reads the license fields of the packages Codes runs.
+ * directory; licensing reads the license fields of the packages Codes runs; the governance pages
+ * (code of conduct, contributing, contributor ladder, moderation) render this repository's
+ * Markdown files as they are.
*/
*/
const fs = require('fs');
const fs = require('fs');
const path = require('path');
const path = require('path');
@@ -22,11 +24,34 @@ const { EVENT_TYPES } = require('../events/outbox');
@@ -22,11 +24,34 @@ const { EVENT_TYPES } = require('../events/outbox');
const ROOT = path.join(__dirname, '..', '..');
const ROOT = path.join(__dirname, '..', '..');
const readJson = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } };
const readJson = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } };
+/**
+ * Governance documents: Markdown in this repository (the same files GitHub shows), rendered as
+ * they are. A document whose text starts with the draft line is shown with a draft notice, kept out
+ * of search engines and the sitemap; removing that line after the owner's review publishes it.
+ */
+const GOVERNANCE = [
+ { slug: 'code-of-conduct', file: 'CODE_OF_CONDUCT.md', blurb: 'how we treat each other, and how to report a problem' },
+ { slug: 'contributing', file: 'CONTRIBUTING.md', blurb: 'how to report a bug, propose a change and open a pull request' },
+ { slug: 'contributor-ladder', file: 'docs/governance/contributor-ladder.md', blurb: 'participant, contributor, reviewer, maintainer, owner' },
+ { slug: 'moderation', file: 'docs/governance/moderation.md', blurb: 'what is moderated, the steps, and appeals' },
+];
+const DRAFT_LINE = /^\*\*Draft — pending owner review\.\*\*.*$/m;
+function loadGovernance() {
+ return GOVERNANCE.map((g) => {
+ const text = fs.readFileSync(path.join(ROOT, g.file), 'utf8');
+ const title = (text.match(/^# (.+)$/m) || [null, g.slug])[1].trim();
+ const draft = DRAFT_LINE.test(text.split('\n').slice(0, 5).join('\n'));
+ const body = text.replace(/^# .+\n/m, '').replace(DRAFT_LINE, '');
+ return { ...g, title, draft, body };
+ });
+}
+
function createPageRoutes(ctx) {
function createPageRoutes(ctx) {
const { config, docs, releases, trust, network, sso } = ctx;
const { config, docs, releases, trust, network, sso } = ctx;
const r = asyncRouter();
const r = asyncRouter();
const form = express.urlencoded({ extended: false, limit: '32kb' });
const form = express.urlencoded({ extended: false, limit: '32kb' });
const PUBLIC_CACHE = 'public, max-age=300';
const PUBLIC_CACHE = 'public, max-age=300';
+ const governance = loadGovernance();
const page = (req, res, o, status = 200) => send(res, status, { viewer: req.viewer, config, path: req.originalUrl, ...o });
const page = (req, res, o, status = 200) => send(res, status, { viewer: req.viewer, config, path: req.originalUrl, ...o });
r.get('/', (req, res) => {
r.get('/', (req, res) => {
@@ -63,9 +88,21 @@ ${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((
@@ -63,9 +88,21 @@ ${table(['App', 'Kind', 'Version', 'Status', 'Trust', 'Published'], recent.map((
<li><a href="/policy/rfc"><strong>Proposals and decisions</strong></a><span>how a contract, capability or event changes; the decision record</span></li>
<li><a href="/policy/rfc"><strong>Proposals and decisions</strong></a><span>how a contract, capability or event changes; the decision record</span></li>
<li><a href="/policy/compatibility"><strong>Compatibility and deprecation</strong></a><span>ADR-002 and ADR-016, as published</span></li>
<li><a href="/policy/compatibility"><strong>Compatibility and deprecation</strong></a><span>ADR-002 and ADR-016, as published</span></li>
<li><a href="/policy/licensing"><strong>Licensing</strong></a><span>what each package is licensed under</span></li>
<li><a href="/policy/licensing"><strong>Licensing</strong></a><span>what each package is licensed under</span></li>
-<li><a href="/policy/transparency"><strong>Transparency</strong></a><span>what Codes stores, what it does not, and what works today</span></li></ul>`,
+<li><a href="/policy/transparency"><strong>Transparency</strong></a><span>what Codes stores, what it does not, and what works today</span></li></ul>
+<h2>Community</h2><ul class="cards">
+${governance.map((g) => html`<li><a href="/policy/${g.slug}"><strong>${g.title}</strong></a><span>${g.blurb}${g.draft ? ' (draft, pending owner review)' : ''}</span></li>`)}</ul>`,
}));
}));
+ for (const g of governance) {
+ r.get(`/policy/${g.slug}`, (req, res) => page(req, res, {
+ index: !g.draft, cache: PUBLIC_CACHE, title: g.title, crumbs: [{ label: 'Policy', href: '/policy' }, { label: g.title }],
+ body: html`<h1>${g.title}</h1>
+${g.draft ? notice(html`<strong>Draft — pending owner review.</strong> This is a proposal, not yet in effect. It takes effect when the owner of the OpenVibers organization approves it.`, 'warn') : ''}
+<article class="prose">${raw(markdown(g.body, { headingOffset: 0 }))}</article>
+<p class="muted small">Source: <a href="https://github.com/OpenVibers/OpenVibe.Codes/blob/main/${g.file}"><code>${g.file}</code></a>. Changes go through a pull request that the owner approves.</p>`,
+ }));
+ }
+
r.get('/policy/rfc', (req, res) => page(req, res, {
r.get('/policy/rfc', (req, res) => page(req, res, {
index: true, cache: PUBLIC_CACHE, title: 'Proposals and decisions', crumbs: [{ label: 'Policy', href: '/policy' }, { label: 'Proposals' }],
index: true, cache: PUBLIC_CACHE, title: 'Proposals and decisions', crumbs: [{ label: 'Policy', href: '/policy' }, { label: 'Proposals' }],
body: html`<h1>Proposals and decisions</h1>
body: html`<h1>Proposals and decisions</h1>
@@ -232,6 +269,7 @@ ${signedIn ? releaseActions(req, rel) : ''}
@@ -232,6 +269,7 @@ ${signedIn ? releaseActions(req, rel) : ''}
r.get('/sitemap.xml', (req, res) => {
r.get('/sitemap.xml', (req, res) => {
const urls = ['/', '/docs', '/docs/contracts', '/docs/capabilities', '/docs/events', '/docs/services', '/docs/sdk', '/oauth', '/tools/webhooks', '/manifests/validate',
const urls = ['/', '/docs', '/docs/contracts', '/docs/capabilities', '/docs/events', '/docs/services', '/docs/sdk', '/oauth', '/tools/webhooks', '/manifests/validate',
'/policy', '/policy/rfc', '/policy/compatibility', '/policy/licensing', '/policy/transparency',
'/policy', '/policy/rfc', '/policy/compatibility', '/policy/licensing', '/policy/transparency',
+ ...governance.filter((g) => !g.draft).map((g) => `/policy/${g.slug}`),
...docs.contracts.map((c) => `/docs/contracts/${c.id}`), ...docs.capabilities.map((c) => `/docs/capabilities/${c.id}`),
...docs.contracts.map((c) => `/docs/contracts/${c.id}`), ...docs.capabilities.map((c) => `/docs/capabilities/${c.id}`),
...docs.sdk.map((m) => `/docs/sdk/${m.slug}`), ...docs.adrs.map((a) => `/docs/adr/${a.id}`)];
...docs.sdk.map((m) => `/docs/sdk/${m.slug}`), ...docs.adrs.map((a) => `/docs/adr/${a.id}`)];
const x = (s) => s.replace(/&/g, '&').replace(/</g, '<');
const x = (s) => s.replace(/&/g, '&').replace(/</g, '<');
@@ -243,4 +281,4 @@ ${signedIn ? releaseActions(req, rel) : ''}
@@ -243,4 +281,4 @@ ${signedIn ? releaseActions(req, rel) : ''}
const statusBadge = (s) => badge(s, s === 'published' ? 'ok' : (s === 'revoked' ? 'bad' : (s === 'deprecated' ? 'warn' : '')));
const statusBadge = (s) => badge(s, s === 'published' ? 'ok' : (s === 'revoked' ? 'bad' : (s === 'deprecated' ? 'warn' : '')));
-module.exports = { createPageRoutes, statusBadge };
+module.exports = { createPageRoutes, statusBadge, GOVERNANCE };
new file mode 100644
new file mode 100644
@@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
+'use strict';
+/**
+ * Governance pages (roadmap §15.5): code of conduct, contribution guide, contributor ladder and
+ * moderation policy are this repository's Markdown files, served under /policy as they are. Each is
+ * a draft pending the owner's review: marked at the top of the file and on the page, kept out of
+ * search engines and the sitemap. Links between them resolve.
+ */
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const { GOVERNANCE } = require('../server/http/pages');
+const { boot, check, done } = require('./helpers/boot');
+
+const ROOT = path.join(__dirname, '..');
+
+(async () => {
+ const t = await boot();
+
+ await check('the four documents exist and each starts with the draft line', async () => {
+ assert.deepStrictEqual(GOVERNANCE.map((g) => g.slug), ['code-of-conduct', 'contributing', 'contributor-ladder', 'moderation']);
+ for (const g of GOVERNANCE) {
+ const lines = fs.readFileSync(path.join(ROOT, g.file), 'utf8').split('\n');
+ assert.match(lines[0], /^# \S/, `${g.file} starts with its title`);
+ assert.match(lines[2], /^\*\*Draft — pending owner review\.\*\*/, `${g.file} is marked as a draft at the top`);
+ }
+ });
+
+ await check('each is served under /policy with the draft notice, rendered Markdown and its source', async () => {
+ for (const g of GOVERNANCE) {
+ const r = await t.get(`/policy/${g.slug}`);
+ assert.strictEqual(r.status, 200, g.slug);
+ assert.match(r.text, /<h1>[^<]+<\/h1>/);
+ assert.match(r.text, /class="notice warn"[^>]*><strong>Draft — pending owner review\.<\/strong>/, `${g.slug} shows the draft notice`);
+ assert.match(r.text, /<meta name="robots" content="noindex, nofollow">/, `${g.slug} is not indexed while a draft`);
+ assert.match(r.text, /<h2>[^<]+<\/h2>/, `${g.slug} has its sections as h2`);
+ assert.ok(r.text.includes(`https://github.com/OpenVibers/OpenVibe.Codes/blob/main/${g.file}`));
+ assert.ok(!/<script(?![^>]*src=)/.test(r.text.split('<article class="prose">')[1].split('</article>')[0]), 'no inline script from Markdown');
+ }
+ });
+
+ await check('the policy index lists them as drafts; the sitemap leaves drafts out', async () => {
+ const idx = await t.get('/policy');
+ for (const g of GOVERNANCE) assert.ok(idx.text.includes(`href="/policy/${g.slug}"`), g.slug);
+ assert.match(idx.text, /\(draft, pending owner review\)/);
+ const map = await t.get('/sitemap.xml');
+ for (const g of GOVERNANCE) assert.ok(!map.text.includes(`/policy/${g.slug}<`), `${g.slug} not in the sitemap while a draft`);
+ assert.ok(map.text.includes('/policy/compatibility<'));
+ });
+
+ await check('every openvibe.codes link in the documents resolves', async () => {
+ const links = new Set();
+ for (const g of GOVERNANCE) {
+ const text = fs.readFileSync(path.join(ROOT, g.file), 'utf8');
+ for (const m of text.matchAll(/\]\(https:\/\/openvibe\.codes(\/[^)\s#]*)\)/g)) links.add(m[1]);
+ }
+ assert.ok(links.size >= 5);
+ for (const l of links) assert.strictEqual((await t.get(l)).status, 200, l);
+ });
+
+ await t.close();
+ done();
+})();