openvibers/openvibe.contracts Commits

v0.27.0: OpenVibe.Codes is alpha — codes.app-manifest@1, codes.release.manage|read

OP OpenVibers committed 23 Sept 2026 · 742db689f4 · 10 files, +384 −7


Changes

ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 6e38e6f5..e8b1ef7d 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -157,6 +157,16 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-013"
++ },
++ {
++ "id": "codes.app-manifest",
++ "version": "1.0.0",
++ "owner": "codes",
++ "schema": "codes/app-manifest.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-014"
}
]
}
ddiff --git a/contracts/codes/app-manifest.v1.json b/contracts/codes/app-manifest.v1.json
nnew file mode 100644
iindex 00000000..35f2c417
---- /dev/null
++++ b/contracts/codes/app-manifest.v1.json
@@@ -0,0 +1,68 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/codes/app-manifest.v1.json",
++ "title": "AppManifest",
++ "description": "PROPOSAL (OpenVibe.Codes, roadmap Wave 20): an app release as the platform knows it. The app is a Network developer app (ADR-014): its id is the Network app id, its capabilities are only a request (the grants in Network are the authority), and trust tiers are metadata that never change a grant check. OpenVibe.Codes validates with this schema, loaded into openvibe-contracts' validator, until Contracts publishes it as codes.app-manifest@1.",
++ "type": "object",
++ "required": ["id", "name", "version", "publisher", "project_id", "capabilities", "compatibility"],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The Network app id (its client_id). Its principal subject is app:<id>."
++ },
++ "name": { "type": "string", "minLength": 1, "maxLength": 80 },
++ "version": {
++ "type": "string",
++ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$",
++ "description": "Semantic version of this release."
++ },
++ "description": { "type": "string", "maxLength": 1000 },
++ "publisher": {
++ "$ref": "../identity/subject-ref.v1.json",
++ "description": "Who publishes the release: the app itself or a member of its project."
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The Network project that owns the app. Tenancy in other services is keyed by it (ADR-014)."
++ },
++ "environment": { "enum": ["sandbox", "production"], "description": "The app's environment. Apps never change environment." },
++ "capabilities": {
++ "description": "Capability ids the app asks for (3+ segments). Only active public (or partner, by staff allowance) capabilities can ever be granted to apps.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$" }
++ },
++ "events": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "consumes": {
++ "description": "Event types (or a trailing .* family) the app subscribes to.",
++ "type": "array", "maxItems": 64, "uniqueItems": true,
++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*(\\.\\*)?$" }
++ }
++ }
++ },
++ "redirect_uris": {
++ "description": "Informational copy of the redirect URIs registered in Network (Network's list is the authority).",
++ "type": "array", "maxItems": 10, "uniqueItems": true,
++ "items": { "type": "string", "maxLength": 500 }
++ },
++ "homepage": { "type": "string", "pattern": "^https://[^\\s]{1,2000}$" },
++ "repository": { "type": "string", "pattern": "^https://[^\\s]{1,2000}$" },
++ "license": { "type": "string", "minLength": 1, "maxLength": 100, "description": "SPDX license expression." },
++ "compatibility": {
++ "type": "object",
++ "required": ["contracts"],
++ "additionalProperties": false,
++ "properties": {
++ "contracts": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Semver range of openvibe-contracts releases it was built against." },
++ "sdk": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Semver range of openvibe-sdk releases it uses." }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/codes.app-manifest/invalid/bad-id.json b/fixtures/codes.app-manifest/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..f1349995
---- /dev/null
++++ b/fixtures/codes.app-manifest/invalid/bad-id.json
@@@ -0,0 +1,16 @@
++{
++ "id": "app_lowercase",
++ "name": "My app",
++ "version": "0.1.0",
++ "publisher": {
++ "type": "app",
++ "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0"
++ },
++ "project_id": "prj_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "environment": "sandbox",
++ "capabilities": [],
++ "compatibility": {
++ "contracts": "^0.26.0",
++ "sdk": "^0.2.2"
++ }
++}
\\ No newline at end of file
ddiff --git a/fixtures/codes.app-manifest/invalid/no-contracts-range.json b/fixtures/codes.app-manifest/invalid/no-contracts-range.json
nnew file mode 100644
iindex 00000000..f4d2784a
---- /dev/null
++++ b/fixtures/codes.app-manifest/invalid/no-contracts-range.json
@@@ -0,0 +1,13 @@
++{
++ "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "name": "My app",
++ "version": "0.1.0",
++ "publisher": {
++ "type": "app",
++ "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0"
++ },
++ "project_id": "prj_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "environment": "sandbox",
++ "capabilities": [],
++ "compatibility": {}
++}
\\ No newline at end of file
ddiff --git a/fixtures/codes.app-manifest/valid/starter.json b/fixtures/codes.app-manifest/valid/starter.json
nnew file mode 100644
iindex 00000000..791c321c
---- /dev/null
++++ b/fixtures/codes.app-manifest/valid/starter.json
@@@ -0,0 +1,16 @@
++{
++ "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "name": "My app",
++ "version": "0.1.0",
++ "publisher": {
++ "type": "app",
++ "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0"
++ },
++ "project_id": "prj_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "environment": "sandbox",
++ "capabilities": [],
++ "compatibility": {
++ "contracts": "^0.26.0",
++ "sdk": "^0.2.2"
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 26c39fde..37f14cbb 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -1837,6 +1837,136 @@
}
}
}
++ },
++ "codes.app-manifest": {
++ "version": "1.0.0",
++ "owner": "codes",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/codes/app-manifest.v1.json",
++ "title": "AppManifest",
++ "description": "PROPOSAL (OpenVibe.Codes, roadmap Wave 20): an app release as the platform knows it. The app is a Network developer app (ADR-014): its id is the Network app id, its capabilities are only a request (the grants in Network are the authority), and trust tiers are metadata that never change a grant check. OpenVibe.Codes validates with this schema, loaded into openvibe-contracts' validator, until Contracts publishes it as codes.app-manifest@1.",
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "version",
++ "publisher",
++ "project_id",
++ "capabilities",
++ "compatibility"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The Network app id (its client_id). Its principal subject is app:<id>."
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "version": {
++ "type": "string",
++ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$",
++ "description": "Semantic version of this release."
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 1000
++ },
++ "publisher": {
++ "$ref": "../identity/subject-ref.v1.json",
++ "description": "Who publishes the release: the app itself or a member of its project."
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The Network project that owns the app. Tenancy in other services is keyed by it (ADR-014)."
++ },
++ "environment": {
++ "enum": [
++ "sandbox",
++ "production"
++ ],
++ "description": "The app's environment. Apps never change environment."
++ },
++ "capabilities": {
++ "description": "Capability ids the app asks for (3+ segments). Only active public (or partner, by staff allowance) capabilities can ever be granted to apps.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"
++ }
++ },
++ "events": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "consumes": {
++ "description": "Event types (or a trailing .* family) the app subscribes to.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*(\\.\\*)?$"
++ }
++ }
++ }
++ },
++ "redirect_uris": {
++ "description": "Informational copy of the redirect URIs registered in Network (Network's list is the authority).",
++ "type": "array",
++ "maxItems": 10,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "maxLength": 500
++ }
++ },
++ "homepage": {
++ "type": "string",
++ "pattern": "^https://[^\\s]{1,2000}$"
++ },
++ "repository": {
++ "type": "string",
++ "pattern": "^https://[^\\s]{1,2000}$"
++ },
++ "license": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "SPDX license expression."
++ },
++ "compatibility": {
++ "type": "object",
++ "required": [
++ "contracts"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "contracts": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "Semver range of openvibe-contracts releases it was built against."
++ },
++ "sdk": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "Semver range of openvibe-sdk releases it uses."
++ }
++ }
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 2782980f..72271a3f 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -3298,3 +3298,76 @@ export interface ModManifest {
};
homepage?: string;
}
++
++/** [email protected] (owner: codes) */
++/**
++ * PROPOSAL (OpenVibe.Codes, roadmap Wave 20): an app release as the platform knows it. The app is a Network developer app (ADR-014): its id is the Network app id, its capabilities are only a request (the grants in Network are the authority), and trust tiers are metadata that never change a grant check. OpenVibe.Codes validates with this schema, loaded into openvibe-contracts' validator, until Contracts publishes it as codes.app-manifest@1.
++ */
++export interface AppManifest {
++ /**
++ * The Network app id (its client_id). Its principal subject is app:<id>.
++ */
++ id: string;
++ name: string;
++ /**
++ * Semantic version of this release.
++ */
++ version: string;
++ description?: string;
++ publisher: SubjectRef;
++ /**
++ * The Network project that owns the app. Tenancy in other services is keyed by it (ADR-014).
++ */
++ project_id: string;
++ /**
++ * The app's environment. Apps never change environment.
++ */
++ environment?: "sandbox" | "production";
++ /**
++ * Capability ids the app asks for (3+ segments). Only active public (or partner, by staff allowance) capabilities can ever be granted to apps.
++ *
++ * @maxItems 64
++ */
++ capabilities: string[];
++ events?: {
++ /**
++ * Event types (or a trailing .* family) the app subscribes to.
++ *
++ * @maxItems 64
++ */
++ consumes?: string[];
++ };
++ /**
++ * Informational copy of the redirect URIs registered in Network (Network's list is the authority).
++ *
++ * @maxItems 10
++ */
++ redirect_uris?:
++ | []
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string];
++ homepage?: string;
++ repository?: string;
++ /**
++ * SPDX license expression.
++ */
++ license?: string;
++ compatibility: {
++ /**
++ * Semver range of openvibe-contracts releases it was built against.
++ */
++ contracts: string;
++ /**
++ * Semver range of openvibe-sdk releases it uses.
++ */
++ sdk?: string;
++ };
++}
ddiff --git a/manifests/capabilities/codes.release.manage.json b/manifests/capabilities/codes.release.manage.json
nnew file mode 100644
iindex 00000000..5c7573c2
---- /dev/null
++++ b/manifests/capabilities/codes.release.manage.json
@@@ -0,0 +1,19 @@
++{
++ "id": "codes.release.manage",
++ "version": "1.0",
++ "owner": "codes",
++ "status": "active",
++ "visibility": "public",
++ "description": "An app manages release metadata for ITSELF with its own token (audience openvibe.codes): create a release from a validated app or mod manifest, publish, deprecate (with a reason and an optional replacement) and revoke it. The token's sub must be app:<the app in the path>; people manage releases in the portal with their project role instead. Trust tiers are not part of this capability (staff metadata only).",
++ "inputSchema": "codes.app-manifest@1",
++ "permissions": ["codes:releases:write"],
++ "resourceConstraints": ["owner", "project"],
++ "quotaClass": "codes-release-write",
++ "events": ["codes.app.published", "codes.app.deprecated", "codes.app.revoked"],
++ "implementedBy": [
++ "POST /api/v1/apps/:app/releases",
++ "POST /api/v1/releases/:id/publish",
++ "POST /api/v1/releases/:id/deprecate",
++ "POST /api/v1/releases/:id/revoke"
++ ]
++}
ddiff --git a/manifests/capabilities/codes.release.read.json b/manifests/capabilities/codes.release.read.json
nnew file mode 100644
iindex 00000000..5eb68773
---- /dev/null
++++ b/manifests/capabilities/codes.release.read.json
@@@ -0,0 +1,18 @@
++{
++ "id": "codes.release.read",
++ "version": "1.0",
++ "owner": "codes",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read public release metadata: an app's published, deprecated and revoked releases with their manifests, compatibility ranges and the app's trust tier (metadata only; it never grants anything). Drafts are never returned. Public; no token needed.",
++ "outputSchema": "codes.app-manifest@1",
++ "permissions": ["codes:releases:read"],
++ "resourceConstraints": ["none"],
++ "quotaClass": "codes-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/apps/:app/releases",
++ "GET /api/v1/apps/:app/trust",
++ "GET /api/v1/releases/:id"
++ ]
++}
ddiff --git a/manifests/services/codes.json b/manifests/services/codes.json
iindex f6c39ab5..47dcf679 100644
---- a/manifests/services/codes.json
++++ b/manifests/services/codes.json
@@@ -1,16 +1,30 @@
{
"version": "0.1.0",
"contractRanges": {
-- "openvibe-contracts": "^0.1.0"
++ "openvibe-contracts": ">=0.26.0 <1.0.0"
},
"id": "codes",
"name": "OpenVibe.Codes",
-- "status": "placeholder",
++ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Codes",
-- "domains": [],
-- "capabilities": [],
-- "eventsProduced": [],
++ "domains": [
++ "openvibe.codes"
++ ],
++ "publicOrigin": "https://openvibe.codes",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "capabilities": [
++ "codes.release.read",
++ "codes.release.manage"
++ ],
++ "eventsProduced": [
++ "codes.app.published",
++ "codes.app.deprecated",
++ "codes.app.revoked"
++ ],
"eventsConsumed": [],
-- "namespacesOwned": [],
-- "notes": "The developer portal: registry and contract explorers, credentials, playgrounds, examples, mod/app publishing, governance. Charter only; no runtime."
++ "namespacesOwned": [
++ "codes.*"
++ ],
++ "notes": "Port 4900, unit openvibe-codes.service, state /var/lib/openvibe-codes. The developer portal (ADR-014): projects, members, apps, credentials, grants and quotas are OWNED BY NETWORK and shown through Network's /api/v1/projects API with the signed-in person's own token; Codes stores none of them and never a secret. Codes owns only manifests, releases (+ append-only release_log), trust tiers (metadata) and playground run logs in its own SQLite. Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client codes, JWKS, the projects API, the registry, client-credentials) and publishes through OpenVibe.Events (events.event.publish). Playgrounds call Events and Media with the APP's own token only. Docs are generated at boot from the pinned openvibe-contracts and openvibe-sdk."
}