openvibers/openvibe.contracts Commits

v0.18.0: OpenVibe.Blog is alpha (blog.* capabilities)

OP OpenVibers committed 23 Sept 2026 · 86f0a01fc0 · 14 files, +299 −8


Changes

ddiff --git a/manifests/capabilities/blog.blog.configure.json b/manifests/capabilities/blog.blog.configure.json
nnew file mode 100644
iindex 00000000..26ad1a13
---- /dev/null
++++ b/manifests/capabilities/blog.blog.configure.json
@@@ -0,0 +1,19 @@
++{
++ "id": "blog.blog.configure",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Change a blog's title, description, language and feed settings (RSS, Atom, JSON Feed on/off, item count, full text or summaries). Only an owner of the blog (or staff on the official blog) acting through X-OV-Subject.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [],
++ "implementedBy": [
++ "PATCH /api/v1/blogs/:handle"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.blog.create.json b/manifests/capabilities/blog.blog.create.json
nnew file mode 100644
iindex 00000000..d56af461
---- /dev/null
++++ b/manifests/capabilities/blog.blog.create.json
@@@ -0,0 +1,19 @@
++{
++ "id": "blog.blog.create",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Get-or-create the blog of the acting member (X-OV-Subject, a usr_ subject): one member blog per subject, created on first use, owned by that subject, handle = the Network username unless another free handle is given. The charter's blog.create as a three-segment id. Browsers use their Network user JWT instead.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [],
++ "implementedBy": [
++ "POST /api/v1/blogs"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.feed.read.json b/manifests/capabilities/blog.feed.read.json
nnew file mode 100644
iindex 00000000..e4b7f278
---- /dev/null
++++ b/manifests/capabilities/blog.feed.read.json
@@@ -0,0 +1,19 @@
++{
++ "id": "blog.feed.read",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Read a blog's JSON Feed through the API (public, published, listable posts only; the same items as /@handle/feed.json). Browsers and crawlers read the public RSS, Atom and JSON Feed URLs without any capability.",
++ "permissions": [
++ "blog:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "blog-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/blogs/:handle/feed"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.member.manage.json b/manifests/capabilities/blog.member.manage.json
nnew file mode 100644
iindex 00000000..cc5325f3
---- /dev/null
++++ b/manifests/capabilities/blog.member.manage.json
@@@ -0,0 +1,21 @@
++{
++ "id": "blog.member.manage",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "List, add, change and remove a blog's members (owner, editor, author) by Network subject. A member blog keeps at least one owner. Owners only.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/blogs/:handle/members",
++ "PUT /api/v1/blogs/:handle/members/:subject",
++ "DELETE /api/v1/blogs/:handle/members/:subject"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.create.json b/manifests/capabilities/blog.post.create.json
nnew file mode 100644
iindex 00000000..4b8e023e
---- /dev/null
++++ b/manifests/capabilities/blog.post.create.json
@@@ -0,0 +1,21 @@
++{
++ "id": "blog.post.create",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Create a draft post (revision 1) on a blog the acting member belongs to. With X-OV-Origin: ai the draft is AI-generated output (OpenVibe.AI blog.draft_post; authorship.workflow { id, runId } required): never attributed to the person, noindex, and not publishable until a person reviews it.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.created"
++ ],
++ "implementedBy": [
++ "POST /api/v1/blogs/:handle/posts"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.delete.json b/manifests/capabilities/blog.post.delete.json
nnew file mode 100644
iindex 00000000..1656d849
---- /dev/null
++++ b/manifests/capabilities/blog.post.delete.json
@@@ -0,0 +1,23 @@
++{
++ "id": "blog.post.delete",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Delete a post: readers get 410, Search a tombstone, the Community thread is hidden; immutable revisions are kept. Editors, owners, the author, and staff (moderation).",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.deleted",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
++ "implementedBy": [
++ "DELETE /api/v1/posts/:id"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.publish.json b/manifests/capabilities/blog.post.publish.json
nnew file mode 100644
iindex 00000000..0239c26c
---- /dev/null
++++ b/manifests/capabilities/blog.post.publish.json
@@@ -0,0 +1,24 @@
++{
++ "id": "blog.post.publish",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Make a revision the published one (idempotent). AI-generated revisions are refused (409 post.review_required) until a person has recorded an approving review.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.published",
++ "blog.post.updated",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
++ "implementedBy": [
++ "POST /api/v1/posts/:id/publish"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.read.json b/manifests/capabilities/blog.post.read.json
nnew file mode 100644
iindex 00000000..98d59b23
---- /dev/null
++++ b/manifests/capabilities/blog.post.read.json
@@@ -0,0 +1,23 @@
++{
++ "id": "blog.post.read",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Read posts that are not public (drafts, scheduled, members-only, private), their revisions and diffs, for the member in X-OV-Subject. Published public posts need no capability.",
++ "permissions": [
++ "blog:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/posts/:id",
++ "GET /api/v1/posts/:id/revisions",
++ "GET /api/v1/posts/:id/revisions/:n",
++ "GET /api/v1/posts/:id/diff",
++ "GET /api/v1/blogs/:handle/posts?all=1"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.schedule.json b/manifests/capabilities/blog.post.schedule.json
nnew file mode 100644
iindex 00000000..f5488177
---- /dev/null
++++ b/manifests/capabilities/blog.post.schedule.json
@@@ -0,0 +1,25 @@
++{
++ "id": "blog.post.schedule",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Schedule a publish of a revision (or an unpublish) at a future time, or cancel it. Idempotent per (post, action, time, revision); applied by Blog's worker with leases, so a restart never publishes twice; a job that cannot succeed emits blog.schedule.failed.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.published",
++ "blog.schedule.failed",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
++ "implementedBy": [
++ "POST /api/v1/posts/:id/schedule",
++ "DELETE /api/v1/posts/:id/schedule"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.unpublish.json b/manifests/capabilities/blog.post.unpublish.json
nnew file mode 100644
iindex 00000000..19a13dea
---- /dev/null
++++ b/manifests/capabilities/blog.post.unpublish.json
@@@ -0,0 +1,23 @@
++{
++ "id": "blog.post.unpublish",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Take a published post down (readers get 404, Search a tombstone, feeds and sitemaps drop it). Editors, owners, the author, and staff (moderation).",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.unpublished",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
++ "implementedBy": [
++ "POST /api/v1/posts/:id/unpublish"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.post.update.json b/manifests/capabilities/blog.post.update.json
nnew file mode 100644
iindex 00000000..2e8905f5
---- /dev/null
++++ b/manifests/capabilities/blog.post.update.json
@@@ -0,0 +1,26 @@
++{
++ "id": "blog.post.update",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Edit a post: content becomes a new immutable revision (expected_revision required; 412 revision.conflict when someone saved first), slug changes leave 301 redirects, visibility, taxonomy, series, media attachments (OpenVibe.Media object ids) and revert as a new revision. Authors edit their own posts; editors and owners every post of the blog.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.updated",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
++ "implementedBy": [
++ "PATCH /api/v1/posts/:id",
++ "POST /api/v1/posts/:id/revert",
++ "POST /api/v1/posts/:id/attachments",
++ "DELETE /api/v1/posts/:id/attachments/:aid"
++ ]
++}
ddiff --git a/manifests/capabilities/blog.theme.set.json b/manifests/capabilities/blog.theme.set.json
nnew file mode 100644
iindex 00000000..cfde19aa
---- /dev/null
++++ b/manifests/capabilities/blog.theme.set.json
@@@ -0,0 +1,19 @@
++{
++ "id": "blog.theme.set",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Choose a blog's theme from the small set of openvibe-shared token presets (vibe, paper, slate, sand, nord, high-contrast). There is no site-local theme engine and no custom CSS. Owners only.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "blog-write",
++ "events": [],
++ "implementedBy": [
++ "PUT /api/v1/blogs/:handle/theme"
++ ]
++}
ddiff --git a/manifests/services/blog.json b/manifests/services/blog.json
iindex 639bd023..c7e7b3bb 100644
---- a/manifests/services/blog.json
++++ b/manifests/services/blog.json
@@@ -1,16 +1,45 @@
{
"version": "0.1.0",
"contractRanges": {
-- "openvibe-contracts": "^0.1.0"
++ "openvibe-contracts": ">=0.13.0 <1.0.0"
},
"id": "blog",
"name": "OpenVibe.Blog",
-- "status": "placeholder",
++ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Blog",
-- "domains": [],
-- "capabilities": [],
-- "eventsProduced": [],
++ "domains": [
++ "openvibe.blog"
++ ],
++ "publicOrigin": "https://openvibe.blog",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "capabilities": [
++ "blog.blog.create",
++ "blog.blog.configure",
++ "blog.theme.set",
++ "blog.member.manage",
++ "blog.post.create",
++ "blog.post.read",
++ "blog.post.update",
++ "blog.post.publish",
++ "blog.post.schedule",
++ "blog.post.unpublish",
++ "blog.post.delete",
++ "blog.feed.read"
++ ],
++ "eventsProduced": [
++ "blog.post.created",
++ "blog.post.published",
++ "blog.post.updated",
++ "blog.post.unpublished",
++ "blog.post.deleted",
++ "blog.schedule.failed",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
"eventsConsumed": [],
-- "namespacesOwned": [],
-- "notes": "The official OpenVibe blog and a blog for every member: drafts, revisions, series, scheduling, feeds. Charter only; no runtime."
++ "namespacesOwned": [
++ "blog.*"
++ ],
++ "notes": "Port 4810. Owns blogs, blog_memberships, blog_posts, blog_post_revisions, blog_series, blog_taxonomy, blog_post_terms, blog_schedules, blog_redirects, blog_feed_settings in its own SQLite (openvibe-publishing packages, prefix blog). Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Community (community.comment.write, community.comment.moderate), OpenVibe.Media (media.object.read, namespace blog) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads blog.index_document.* from Events."
}
ddiff --git a/package.json b/package.json
iindex fcf89aa9..7283ef6d 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.17.0",
++ "version": "0.18.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",