openvibers/openvibe.contracts Commits

Contracts 0.116.0: OpenVibe.Food, OpenVibe.Help and OpenVibe.Work become services (plan T19) (#63)

OP OpenVibers committed 8 Oct 2026 · fe3434154a · 69 files, +2128 −93

- manifests/services/{food,help,work}.json: ports 4970/5020/4960, live, site positions 24-26; products point at them.
- food.plan.read/write and food.pantry.read/write with food.plan-create-request@1, food.plan@1, food.plan-result@1,
  food.pantry-request@1, food.pantry-result@1 and fixtures.


Claude-Session: https://claude.ai/code/session_01K8AYn3FF3HE7CPfM262qdh

Co-authored-by: Claude Opus 5.5 <[email protected]>

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex e3bebd00..4bcb2dc5 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,21 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.116.0 — 2026-10-08
++
++**OpenVibe.Food, OpenVibe.Help and OpenVibe.Work become services** (plan T19 "flesh the network out"; owner 2026-10-08).
++- `manifests/services/food.json` (port 4970, `openvibe.food` live, site position 24): food banks and budget grocers
++ near you from OpenStreetMap (ODbL attribution on every answer), the budget food list, meal plans and a pantry.
++- `manifests/services/help.json` (port 5020, `openvibe.help` live, position 25): the network's help centre built from
++ this repository's catalog, and support tickets. No capabilities: a ticket is a person's own.
++- `manifests/services/work.json` (port 4960, `openvibe.work` live, position 26): job listings from open job boards with
++ their provenance (excerpt only, original linked), search and saved searches. No capabilities yet.
++- Capabilities (audience `openvibe.food`): `food.plan.write` (sensitive), `food.plan.read`, `food.pantry.write`
++ (sensitive), `food.pantry.read`; the place search and the food list are public reads with no capability.
++- Schemas: `food.plan-create-request@1`, `food.plan@1`, `food.plan-result@1`, `food.pantry-request@1`,
++ `food.pantry-result@1`.
++- The three product manifests point at their services.
++
## 0.115.0 — 2026-10-08
**OpenVibe.Actor becomes a service** (plan T17, ADR-044; owner direction 2026-10-08: Actor is OpenVibe's own general agent
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex abf2f49e..640203bd 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -6353,6 +6353,51 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-044"
++ },
++ {
++ "id": "food.pantry-request",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/pantry-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.pantry-result",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/pantry-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.plan",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/plan.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.plan-create-request",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/plan-create-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.plan-result",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/plan-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
}
]
}
ddiff --git a/contracts/food/pantry-request.v1.json b/contracts/food/pantry-request.v1.json
nnew file mode 100644
iindex 00000000..5e3ae795
---- /dev/null
++++ b/contracts/food/pantry-request.v1.json
@@@ -0,0 +1,22 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/pantry-request.v1.json",
++ "title": "FoodPantryRequest",
++ "description": "food.pantry-request@1: PUT /api/v1/pantry on OpenVibe.Food replaces the pantry with exactly these food slugs.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ }
++ }
++ }
++}
ddiff --git a/contracts/food/pantry-result.v1.json b/contracts/food/pantry-result.v1.json
nnew file mode 100644
iindex 00000000..f8735305
---- /dev/null
++++ b/contracts/food/pantry-result.v1.json
@@@ -0,0 +1,110 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/pantry-result.v1.json",
++ "title": "FoodPantryResult",
++ "description": "food.pantry-result@1: GET and PUT /api/v1/pantry on OpenVibe.Food: the slugs, each food as the food list shows it, and the suggestions matched against them (can_make: everything there; almost: one or two items missing, named).",
++ "type": "object",
++ "required": [
++ "items",
++ "foods",
++ "suggestions"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "foods": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "suggestions": {
++ "type": "object",
++ "required": [
++ "can_make",
++ "almost"
++ ],
++ "properties": {
++ "can_make": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "serves": {
++ "type": "integer"
++ },
++ "minutes": {
++ "type": "integer"
++ },
++ "missing": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ "almost": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "serves": {
++ "type": "integer"
++ },
++ "minutes": {
++ "type": "integer"
++ },
++ "missing": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "note": {
++ "type": "string"
++ }
++ }
++}
ddiff --git a/contracts/food/plan-create-request.v1.json b/contracts/food/plan-create-request.v1.json
nnew file mode 100644
iindex 00000000..609a16c7
---- /dev/null
++++ b/contracts/food/plan-create-request.v1.json
@@@ -0,0 +1,39 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/plan-create-request.v1.json",
++ "title": "FoodPlanCreateRequest",
++ "description": "food.plan-create-request@1: POST /api/v1/plans on OpenVibe.Food. people 1-12, days 1-14, budget (US dollars for the whole plan) optional, title optional.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "people",
++ "days"
++ ],
++ "properties": {
++ "people": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ },
++ "days": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 14
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 5000
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ }
++ }
++}
ddiff --git a/contracts/food/plan-result.v1.json b/contracts/food/plan-result.v1.json
nnew file mode 100644
iindex 00000000..ae40e8f1
---- /dev/null
++++ b/contracts/food/plan-result.v1.json
@@@ -0,0 +1,26 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/plan-result.v1.json",
++ "title": "FoodPlanResult",
++ "description": "food.plan-result@1: answers of the plan routes on OpenVibe.Food: GET /api/v1/plans → { plans }; GET /api/v1/plans/:id and POST /api/v1/plans (201) → the plan itself (food.plan@1).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "plans"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "plans": {
++ "type": "array",
++ "items": {
++ "$ref": "plan.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "$ref": "plan.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/food/plan.v1.json b/contracts/food/plan.v1.json
nnew file mode 100644
iindex 00000000..b88d29f0
---- /dev/null
++++ b/contracts/food/plan.v1.json
@@@ -0,0 +1,115 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/plan.v1.json",
++ "title": "FoodPlan",
++ "description": "food.plan@1: one saved plan on OpenVibe.Food.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "title",
++ "people",
++ "days",
++ "budget",
++ "created_at",
++ "plan"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^pln_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ },
++ "people": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "days": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "plan": {
++ "type": "object",
++ "description": "The plan as Food worked it out: spec, items (the shopping list), by_store, menu (one entry a day: breakfast, lunch, dinner, snack), group_coverage and totals (cost, cost_per_person_day, kcal, protein). Food may add fields.",
++ "required": [
++ "spec",
++ "items",
++ "menu",
++ "totals"
++ ],
++ "properties": {
++ "spec": {
++ "type": "object",
++ "required": [
++ "people",
++ "days"
++ ],
++ "properties": {
++ "people": {
++ "type": "integer"
++ },
++ "days": {
++ "type": "integer"
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
++ },
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ },
++ "menu": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "day"
++ ],
++ "properties": {
++ "day": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ "totals": {
++ "type": "object",
++ "required": [
++ "cost"
++ ],
++ "properties": {
++ "cost": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/food.pantry-request/invalid/blank-slug.json b/fixtures/food.pantry-request/invalid/blank-slug.json
nnew file mode 100644
iindex 00000000..b7358290
---- /dev/null
++++ b/fixtures/food.pantry-request/invalid/blank-slug.json
@@@ -0,0 +1,5 @@
++{
++ "items": [
++ ""
++ ]
++}
ddiff --git a/fixtures/food.pantry-request/invalid/not-array.json b/fixtures/food.pantry-request/invalid/not-array.json
nnew file mode 100644
iindex 00000000..cf8d37e2
---- /dev/null
++++ b/fixtures/food.pantry-request/invalid/not-array.json
@@@ -0,0 +1,3 @@
++{
++ "items": "eggs"
++}
ddiff --git a/fixtures/food.pantry-request/valid/empty.json b/fixtures/food.pantry-request/valid/empty.json
nnew file mode 100644
iindex 00000000..2feb2101
---- /dev/null
++++ b/fixtures/food.pantry-request/valid/empty.json
@@@ -0,0 +1,3 @@
++{
++ "items": []
++}
ddiff --git a/fixtures/food.pantry-request/valid/two.json b/fixtures/food.pantry-request/valid/two.json
nnew file mode 100644
iindex 00000000..f9a32551
---- /dev/null
++++ b/fixtures/food.pantry-request/valid/two.json
@@@ -0,0 +1,6 @@
++{
++ "items": [
++ "eggs-dozen",
++ "rice-long-grain-5lb"
++ ]
++}
ddiff --git a/fixtures/food.pantry-result/invalid/no-suggestions.json b/fixtures/food.pantry-result/invalid/no-suggestions.json
nnew file mode 100644
iindex 00000000..31092017
---- /dev/null
++++ b/fixtures/food.pantry-result/invalid/no-suggestions.json
@@@ -0,0 +1,4 @@
++{
++ "items": [],
++ "foods": []
++}
ddiff --git a/fixtures/food.pantry-result/valid/one.json b/fixtures/food.pantry-result/valid/one.json
nnew file mode 100644
iindex 00000000..d660e82d
---- /dev/null
++++ b/fixtures/food.pantry-result/valid/one.json
@@@ -0,0 +1,26 @@
++{
++ "items": [
++ "eggs-dozen"
++ ],
++ "foods": [
++ {
++ "slug": "eggs-dozen",
++ "name": "Eggs, dozen"
++ }
++ ],
++ "suggestions": {
++ "can_make": [],
++ "almost": [
++ {
++ "slug": "egg-fried-rice",
++ "name": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "missing": [
++ "Rice"
++ ]
++ }
++ ]
++ },
++ "note": "Basic suggestions."
++}
ddiff --git a/fixtures/food.plan-create-request/invalid/extra-field.json b/fixtures/food.plan-create-request/invalid/extra-field.json
nnew file mode 100644
iindex 00000000..abe89ace
---- /dev/null
++++ b/fixtures/food.plan-create-request/invalid/extra-field.json
@@@ -0,0 +1,5 @@
++{
++ "people": 2,
++ "days": 2,
++ "owner": "someone"
++}
ddiff --git a/fixtures/food.plan-create-request/invalid/no-days.json b/fixtures/food.plan-create-request/invalid/no-days.json
nnew file mode 100644
iindex 00000000..8c7e993e
---- /dev/null
++++ b/fixtures/food.plan-create-request/invalid/no-days.json
@@@ -0,0 +1,3 @@
++{
++ "people": 2
++}
ddiff --git a/fixtures/food.plan-create-request/invalid/too-many-people.json b/fixtures/food.plan-create-request/invalid/too-many-people.json
nnew file mode 100644
iindex 00000000..3536caa5
---- /dev/null
++++ b/fixtures/food.plan-create-request/invalid/too-many-people.json
@@@ -0,0 +1,4 @@
++{
++ "people": 40,
++ "days": 3
++}
ddiff --git a/fixtures/food.plan-create-request/valid/full.json b/fixtures/food.plan-create-request/valid/full.json
nnew file mode 100644
iindex 00000000..a64f1ddd
---- /dev/null
++++ b/fixtures/food.plan-create-request/valid/full.json
@@@ -0,0 +1,6 @@
++{
++ "people": 4,
++ "days": 7,
++ "budget": 120,
++ "title": "Family week"
++}
ddiff --git a/fixtures/food.plan-create-request/valid/minimal.json b/fixtures/food.plan-create-request/valid/minimal.json
nnew file mode 100644
iindex 00000000..5709d01d
---- /dev/null
++++ b/fixtures/food.plan-create-request/valid/minimal.json
@@@ -0,0 +1,4 @@
++{
++ "people": 1,
++ "days": 3
++}
ddiff --git a/fixtures/food.plan-result/invalid/plans-not-array.json b/fixtures/food.plan-result/invalid/plans-not-array.json
nnew file mode 100644
iindex 00000000..7e116703
---- /dev/null
++++ b/fixtures/food.plan-result/invalid/plans-not-array.json
@@@ -0,0 +1,41 @@
++{
++ "plans": {
++ "id": "pln_01JZ6Q4X8M2N3P4R5S6T7V8W9X",
++ "title": "Week for two",
++ "people": 2,
++ "days": 7,
++ "budget": 80,
++ "created_at": "2026-10-08T20:00:00.000Z",
++ "plan": {
++ "spec": {
++ "people": 2,
++ "days": 7,
++ "budget": 80
++ },
++ "items": [
++ {
++ "slug": "rice-long-grain-5lb",
++ "name": "Long grain rice, 5 lb",
++ "packs": 1
++ }
++ ],
++ "menu": [
++ {
++ "day": 1,
++ "breakfast": [
++ "Oats"
++ ],
++ "lunch": [],
++ "dinner": [
++ "Rice"
++ ],
++ "snack": []
++ }
++ ],
++ "totals": {
++ "cost": 41.2,
++ "kcal": 28000
++ }
++ }
++ }
++}
ddiff --git a/fixtures/food.plan-result/valid/empty-list.json b/fixtures/food.plan-result/valid/empty-list.json
nnew file mode 100644
iindex 00000000..399a5825
---- /dev/null
++++ b/fixtures/food.plan-result/valid/empty-list.json
@@@ -0,0 +1,3 @@
++{
++ "plans": []
++}
ddiff --git a/fixtures/food.plan-result/valid/list.json b/fixtures/food.plan-result/valid/list.json
nnew file mode 100644
iindex 00000000..46c4c5f9
---- /dev/null
++++ b/fixtures/food.plan-result/valid/list.json
@@@ -0,0 +1,43 @@
++{
++ "plans": [
++ {
++ "id": "pln_01JZ6Q4X8M2N3P4R5S6T7V8W9X",
++ "title": "Week for two",
++ "people": 2,
++ "days": 7,
++ "budget": 80,
++ "created_at": "2026-10-08T20:00:00.000Z",
++ "plan": {
++ "spec": {
++ "people": 2,
++ "days": 7,
++ "budget": 80
++ },
++ "items": [
++ {
++ "slug": "rice-long-grain-5lb",
++ "name": "Long grain rice, 5 lb",
++ "packs": 1
++ }
++ ],
++ "menu": [
++ {
++ "day": 1,
++ "breakfast": [
++ "Oats"
++ ],
++ "lunch": [],
++ "dinner": [
++ "Rice"
++ ],
++ "snack": []
++ }
++ ],
++ "totals": {
++ "cost": 41.2,
++ "kcal": 28000
++ }
++ }
++ }
++ ]
++}
ddiff --git a/fixtures/food.plan-result/valid/one.json b/fixtures/food.plan-result/valid/one.json
nnew file mode 100644
iindex 00000000..884ddf2b
---- /dev/null
++++ b/fixtures/food.plan-result/valid/one.json
@@@ -0,0 +1,39 @@
++{
++ "id": "pln_01JZ6Q4X8M2N3P4R5S6T7V8W9X",
++ "title": "Week for two",
++ "people": 2,
++ "days": 7,
++ "budget": 80,
++ "created_at": "2026-10-08T20:00:00.000Z",
++ "plan": {
++ "spec": {
++ "people": 2,
++ "days": 7,
++ "budget": 80
++ },
++ "items": [
++ {
++ "slug": "rice-long-grain-5lb",
++ "name": "Long grain rice, 5 lb",
++ "packs": 1
++ }
++ ],
++ "menu": [
++ {
++ "day": 1,
++ "breakfast": [
++ "Oats"
++ ],
++ "lunch": [],
++ "dinner": [
++ "Rice"
++ ],
++ "snack": []
++ }
++ ],
++ "totals": {
++ "cost": 41.2,
++ "kcal": 28000
++ }
++ }
++}
ddiff --git a/fixtures/food.plan/invalid/bad-id.json b/fixtures/food.plan/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..7cee773e
---- /dev/null
++++ b/fixtures/food.plan/invalid/bad-id.json
@@@ -0,0 +1,39 @@
++{
++ "id": "plan-1",
++ "title": "Week for two",
++ "people": 2,
++ "days": 7,
++ "budget": 80,
++ "created_at": "2026-10-08T20:00:00.000Z",
++ "plan": {
++ "spec": {
++ "people": 2,
++ "days": 7,
++ "budget": 80
++ },
++ "items": [
++ {
++ "slug": "rice-long-grain-5lb",
++ "name": "Long grain rice, 5 lb",
++ "packs": 1
++ }
++ ],
++ "menu": [
++ {
++ "day": 1,
++ "breakfast": [
++ "Oats"
++ ],
++ "lunch": [],
++ "dinner": [
++ "Rice"
++ ],
++ "snack": []
++ }
++ ],
++ "totals": {
++ "cost": 41.2,
++ "kcal": 28000
++ }
++ }
++}
ddiff --git a/fixtures/food.plan/invalid/no-plan.json b/fixtures/food.plan/invalid/no-plan.json
nnew file mode 100644
iindex 00000000..9bba3e92
---- /dev/null
++++ b/fixtures/food.plan/invalid/no-plan.json
@@@ -0,0 +1,8 @@
++{
++ "id": "pln_01JZ6Q4X8M2N3P4R5S6T7V8W9X",
++ "title": "Week for two",
++ "people": 2,
++ "days": 7,
++ "budget": 80,
++ "created_at": "2026-10-08T20:00:00.000Z"
++}
ddiff --git a/fixtures/food.plan/valid/week.json b/fixtures/food.plan/valid/week.json
nnew file mode 100644
iindex 00000000..884ddf2b
---- /dev/null
++++ b/fixtures/food.plan/valid/week.json
@@@ -0,0 +1,39 @@
++{
++ "id": "pln_01JZ6Q4X8M2N3P4R5S6T7V8W9X",
++ "title": "Week for two",
++ "people": 2,
++ "days": 7,
++ "budget": 80,
++ "created_at": "2026-10-08T20:00:00.000Z",
++ "plan": {
++ "spec": {
++ "people": 2,
++ "days": 7,
++ "budget": 80
++ },
++ "items": [
++ {
++ "slug": "rice-long-grain-5lb",
++ "name": "Long grain rice, 5 lb",
++ "packs": 1
++ }
++ ],
++ "menu": [
++ {
++ "day": 1,
++ "breakfast": [
++ "Oats"
++ ],
++ "lunch": [],
++ "dinner": [
++ "Rice"
++ ],
++ "snack": []
++ }
++ ],
++ "totals": {
++ "cost": 41.2,
++ "kcal": 28000
++ }
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex c28e6f61..6bc13f19 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -73477,6 +73477,343 @@
}
}
}
++ },
++ "food.pantry-request": {
++ "version": "1.0.0",
++ "owner": "food",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/pantry-request.v1.json",
++ "title": "FoodPantryRequest",
++ "description": "food.pantry-request@1: PUT /api/v1/pantry on OpenVibe.Food replaces the pantry with exactly these food slugs.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ }
++ }
++ }
++ }
++ },
++ "food.pantry-result": {
++ "version": "1.0.0",
++ "owner": "food",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/pantry-result.v1.json",
++ "title": "FoodPantryResult",
++ "description": "food.pantry-result@1: GET and PUT /api/v1/pantry on OpenVibe.Food: the slugs, each food as the food list shows it, and the suggestions matched against them (can_make: everything there; almost: one or two items missing, named).",
++ "type": "object",
++ "required": [
++ "items",
++ "foods",
++ "suggestions"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "foods": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "suggestions": {
++ "type": "object",
++ "required": [
++ "can_make",
++ "almost"
++ ],
++ "properties": {
++ "can_make": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "serves": {
++ "type": "integer"
++ },
++ "minutes": {
++ "type": "integer"
++ },
++ "missing": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ "almost": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "serves": {
++ "type": "integer"
++ },
++ "minutes": {
++ "type": "integer"
++ },
++ "missing": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "note": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "food.plan": {
++ "version": "1.0.0",
++ "owner": "food",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/plan.v1.json",
++ "title": "FoodPlan",
++ "description": "food.plan@1: one saved plan on OpenVibe.Food.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "title",
++ "people",
++ "days",
++ "budget",
++ "created_at",
++ "plan"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^pln_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ },
++ "people": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "days": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "plan": {
++ "type": "object",
++ "description": "The plan as Food worked it out: spec, items (the shopping list), by_store, menu (one entry a day: breakfast, lunch, dinner, snack), group_coverage and totals (cost, cost_per_person_day, kcal, protein). Food may add fields.",
++ "required": [
++ "spec",
++ "items",
++ "menu",
++ "totals"
++ ],
++ "properties": {
++ "spec": {
++ "type": "object",
++ "required": [
++ "people",
++ "days"
++ ],
++ "properties": {
++ "people": {
++ "type": "integer"
++ },
++ "days": {
++ "type": "integer"
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
++ },
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ },
++ "menu": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "day"
++ ],
++ "properties": {
++ "day": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ "totals": {
++ "type": "object",
++ "required": [
++ "cost"
++ ],
++ "properties": {
++ "cost": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "food.plan-create-request": {
++ "version": "1.0.0",
++ "owner": "food",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/plan-create-request.v1.json",
++ "title": "FoodPlanCreateRequest",
++ "description": "food.plan-create-request@1: POST /api/v1/plans on OpenVibe.Food. people 1-12, days 1-14, budget (US dollars for the whole plan) optional, title optional.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "people",
++ "days"
++ ],
++ "properties": {
++ "people": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ },
++ "days": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 14
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 5000
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ }
++ }
++ }
++ },
++ "food.plan-result": {
++ "version": "1.0.0",
++ "owner": "food",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/plan-result.v1.json",
++ "title": "FoodPlanResult",
++ "description": "food.plan-result@1: answers of the plan routes on OpenVibe.Food: GET /api/v1/plans → { plans }; GET /api/v1/plans/:id and POST /api/v1/plans (201) → the plan itself (food.plan@1).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "plans"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "plans": {
++ "type": "array",
++ "items": {
++ "$ref": "plan.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "$ref": "plan.v1.json"
++ }
++ ]
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 5cba4668..2f2d754e 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 0f4f01a9..9dda79b2 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex cee01e44..e27f3efa 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 3d67f96e..f2c24840 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 453c83e5..8793ce5c 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 35044da0..4daca4bc 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex df893b8b..e079bee6 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 3ed5d1ca..6d9ee943 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex b0bac660..f978bf9c 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 381f5de4..555a99b9 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
nnew file mode 100644
iindex 00000000..ede17e21
---- /dev/null
++++ b/generated/openapi/food.json
@@@ -0,0 +1,656 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Food API",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.food"
++ }
++ ],
++ "paths": {
++ "/api/v1/pantry": {
++ "get": {
++ "operationId": "get_api_v1_pantry",
++ "summary": "Read a caller's pantry with the suggestions matched against it: GET /api/v1/pantry → { items, foods, suggestions: { can_make, almost }, note }.",
++ "description": "**food.pantry.read** (public): Read a caller's pantry with the suggestions matched against it: GET /api/v1/pantry → { items, foods, suggestions: { can_make, almost }, note }. The suggestions are deterministic matches against basic recipes, not dietary advice.",
++ "tags": [
++ "food.pantry"
++ ],
++ "x-openvibe-capabilities": [
++ "food.pantry.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "food-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.pantry.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.pantry-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_api_v1_pantry",
++ "summary": "Replace a caller's pantry with exactly this list: PUT /api/v1/pantry with { items: [food slug, …] } (at most 200, each one in the food list; GET /api/v1/foods lists them).",
++ "description": "**food.pantry.write** (public): Replace a caller's pantry with exactly this list: PUT /api/v1/pantry with { items: [food slug, …] } (at most 200, each one in the food list; GET /api/v1/foods lists them). Answers like a read.",
++ "tags": [
++ "food.pantry"
++ ],
++ "x-openvibe-capabilities": [
++ "food.pantry.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "food-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.pantry.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.pantry-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.pantry-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/plans": {
++ "get": {
++ "operationId": "get_api_v1_plans",
++ "summary": "Read the plans a caller saved: GET /api/v1/plans (newest first, ?limit to 50) → { plans }, and GET /api/v1/plans/:id → one plan.",
++ "description": "**food.plan.read** (public): Read the plans a caller saved: GET /api/v1/plans (newest first, ?limit to 50) → { plans }, and GET /api/v1/plans/:id → one plan. Only the person who saved a plan (or their tokens) can read it.",
++ "tags": [
++ "food.plan"
++ ],
++ "x-openvibe-capabilities": [
++ "food.plan.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "food-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.plan.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.plan-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_api_v1_plans",
++ "summary": "Plan a shop for a household and a budget and save it: POST /api/v1/plans with { people, days, budget?, title?",
++ "description": "**food.plan.write** (public): Plan a shop for a household and a budget and save it: POST /api/v1/plans with { people, days, budget?, title? }. Food picks budget items from its food list to cover each food group for every person and day, splits them by the cheapest store and lays out a menu; the saved plan (201, Location /api/v1/plans/:id) is the caller's own.",
++ "tags": [
++ "food.plan"
++ ],
++ "x-openvibe-capabilities": [
++ "food.plan.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "food-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.plan.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.plan-create-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.plan-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/plans/{id}": {
++ "get": {
++ "operationId": "get_api_v1_plans_id",
++ "summary": "Read the plans a caller saved: GET /api/v1/plans (newest first, ?limit to 50) → { plans }, and GET /api/v1/plans/:id → one plan.",
++ "description": "**food.plan.read** (public): Read the plans a caller saved: GET /api/v1/plans (newest first, ?limit to 50) → { plans }, and GET /api/v1/plans/:id → one plan. Only the person who saved a plan (or their tokens) can read it.",
++ "tags": [
++ "food.plan"
++ ],
++ "x-openvibe-capabilities": [
++ "food.plan.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "food-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.plan.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.plan-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "food.pantry-request.v1": {
++ "title": "FoodPantryRequest",
++ "description": "food.pantry-request@1: PUT /api/v1/pantry on OpenVibe.Food replaces the pantry with exactly these food slugs.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ }
++ }
++ }
++ },
++ "food.pantry-result.v1": {
++ "title": "FoodPantryResult",
++ "description": "food.pantry-result@1: GET and PUT /api/v1/pantry on OpenVibe.Food: the slugs, each food as the food list shows it, and the suggestions matched against them (can_make: everything there; almost: one or two items missing, named).",
++ "type": "object",
++ "required": [
++ "items",
++ "foods",
++ "suggestions"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "foods": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "suggestions": {
++ "type": "object",
++ "required": [
++ "can_make",
++ "almost"
++ ],
++ "properties": {
++ "can_make": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "serves": {
++ "type": "integer"
++ },
++ "minutes": {
++ "type": "integer"
++ },
++ "missing": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ },
++ "almost": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "serves": {
++ "type": "integer"
++ },
++ "minutes": {
++ "type": "integer"
++ },
++ "missing": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "note": {
++ "type": "string"
++ }
++ }
++ },
++ "food.plan-create-request.v1": {
++ "title": "FoodPlanCreateRequest",
++ "description": "food.plan-create-request@1: POST /api/v1/plans on OpenVibe.Food. people 1-12, days 1-14, budget (US dollars for the whole plan) optional, title optional.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "people",
++ "days"
++ ],
++ "properties": {
++ "people": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ },
++ "days": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 14
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 5000
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ }
++ }
++ },
++ "food.plan-result.v1": {
++ "title": "FoodPlanResult",
++ "description": "food.plan-result@1: answers of the plan routes on OpenVibe.Food: GET /api/v1/plans → { plans }; GET /api/v1/plans/:id and POST /api/v1/plans (201) → the plan itself (food.plan@1).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "plans"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "plans": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/food.plan.v1"
++ }
++ }
++ }
++ },
++ {
++ "$ref": "#/components/schemas/food.plan.v1"
++ }
++ ]
++ },
++ "food.plan.v1": {
++ "title": "FoodPlan",
++ "description": "food.plan@1: one saved plan on OpenVibe.Food.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "title",
++ "people",
++ "days",
++ "budget",
++ "created_at",
++ "plan"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^pln_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ },
++ "people": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "days": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "plan": {
++ "type": "object",
++ "description": "The plan as Food worked it out: spec, items (the shopping list), by_store, menu (one entry a day: breakfast, lunch, dinner, snack), group_coverage and totals (cost, cost_per_person_day, kcal, protein). Food may add fields.",
++ "required": [
++ "spec",
++ "items",
++ "menu",
++ "totals"
++ ],
++ "properties": {
++ "spec": {
++ "type": "object",
++ "required": [
++ "people",
++ "days"
++ ],
++ "properties": {
++ "people": {
++ "type": "integer"
++ },
++ "days": {
++ "type": "integer"
++ },
++ "budget": {
++ "type": [
++ "number",
++ "null"
++ ]
++ }
++ }
++ },
++ "items": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
++ },
++ "menu": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "day"
++ ],
++ "properties": {
++ "day": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ "totals": {
++ "type": "object",
++ "required": [
++ "cost"
++ ],
++ "properties": {
++ "cost": {
++ "type": "number",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex bce1ec6f..106868df 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex 7c39d682..88976483 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex f09108fa..b14118b8 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -71,6 +71,13 @@
"operations": 15,
"capabilities": 8
},
++ {
++ "service": "food",
++ "name": "OpenVibe.Food",
++ "origin": "https://openvibe.food",
++ "operations": 5,
++ "capabilities": 4
++ },
{
"service": "games",
"name": "OpenVibe.Games",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex c3db2e93..b467570b 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex 5e549afc..6e97d7eb 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex aca55d2a..5a135d2d 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex b7b41372..ee5fed31 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex d12ec91c..a4a4de57 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.115.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 6b4542b0..750d2dcf 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex c42f406d..d13c1166 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex e38335e3..b60d7481 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex 146d1350..60816e2a 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 1182cf5e..711d92ec 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 837fa66c..4f171ec2 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 9e2a0fee..3fe7d749 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 06b9ec68..b3bb8dca 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 68a04db2..a5d40cbb 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 4ee759ce..6e2fba77 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex 1eaa5785..b0c78ae8 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex a352cef0..92990249 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.115.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.115.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.116.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.116.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 0dbb563f..df7cbb89 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -58953,3 +58953,94 @@ export interface ActorAgentListResult {
reason?: string;
}[];
}
++
++/** [email protected] (owner: food) */
++/**
++ * food.pantry-request@1: PUT /api/v1/pantry on OpenVibe.Food replaces the pantry with exactly these food slugs.
++ */
++export interface FoodPantryRequest {
++ /**
++ * @maxItems 200
++ */
++ items: string[];
++}
++
++/** [email protected] (owner: food) */
++/**
++ * food.pantry-result@1: GET and PUT /api/v1/pantry on OpenVibe.Food: the slugs, each food as the food list shows it, and the suggestions matched against them (can_make: everything there; almost: one or two items missing, named).
++ */
++export interface FoodPantryResult {
++ items: string[];
++ foods: {
++ slug: string;
++ name: string;
++ }[];
++ suggestions: {
++ can_make: {
++ slug: string;
++ name: string;
++ serves?: number;
++ minutes?: number;
++ missing?: string[];
++ }[];
++ almost: {
++ slug: string;
++ name: string;
++ serves?: number;
++ minutes?: number;
++ missing?: string[];
++ }[];
++ };
++ note?: string;
++}
++
++/** [email protected] (owner: food) */
++/**
++ * food.plan@1: one saved plan on OpenVibe.Food.
++ */
++export interface FoodPlan {
++ id: string;
++ title: string | null;
++ people: number;
++ days: number;
++ budget: number | null;
++ created_at: string;
++ /**
++ * The plan as Food worked it out: spec, items (the shopping list), by_store, menu (one entry a day: breakfast, lunch, dinner, snack), group_coverage and totals (cost, cost_per_person_day, kcal, protein). Food may add fields.
++ */
++ plan: {
++ spec: {
++ people: number;
++ days: number;
++ budget?: number | null;
++ };
++ items: {}[];
++ menu: {
++ day: number;
++ }[];
++ totals: {
++ cost: number;
++ };
++ };
++}
++
++/** [email protected] (owner: food) */
++/**
++ * food.plan-create-request@1: POST /api/v1/plans on OpenVibe.Food. people 1-12, days 1-14, budget (US dollars for the whole plan) optional, title optional.
++ */
++export interface FoodPlanCreateRequest {
++ people: number;
++ days: number;
++ budget?: number | null;
++ title?: string | null;
++}
++
++/** [email protected] (owner: food) */
++/**
++ * food.plan-result@1: answers of the plan routes on OpenVibe.Food: GET /api/v1/plans → { plans }; GET /api/v1/plans/:id and POST /api/v1/plans (201) → the plan itself (food.plan@1).
++ */
++export type FoodPlanResult =
++ | {
++ plans: FoodPlan[];
++ }
++ | FoodPlan;
ddiff --git a/manifests/capabilities/food.pantry.read.json b/manifests/capabilities/food.pantry.read.json
nnew file mode 100644
iindex 00000000..abe2c7cc
---- /dev/null
++++ b/manifests/capabilities/food.pantry.read.json
@@@ -0,0 +1,21 @@
++{
++ "id": "food.pantry.read",
++ "version": "1.0",
++ "owner": "food",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read a caller's pantry with the suggestions matched against it: GET /api/v1/pantry → { items, foods, suggestions: { can_make, almost }, note }. The suggestions are deterministic matches against basic recipes, not dietary advice.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "food.pantry-result@1",
++ "permissions": [
++ "food:pantry:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "food-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/pantry"
++ ]
++}
ddiff --git a/manifests/capabilities/food.pantry.write.json b/manifests/capabilities/food.pantry.write.json
nnew file mode 100644
iindex 00000000..0aa9b69c
---- /dev/null
++++ b/manifests/capabilities/food.pantry.write.json
@@@ -0,0 +1,22 @@
++{
++ "id": "food.pantry.write",
++ "version": "1.0",
++ "owner": "food",
++ "status": "active",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "Replace a caller's pantry with exactly this list: PUT /api/v1/pantry with { items: [food slug, …] } (at most 200, each one in the food list; GET /api/v1/foods lists them). Answers like a read.",
++ "inputSchema": "food.pantry-request@1",
++ "outputSchema": "food.pantry-result@1",
++ "permissions": [
++ "food:pantry:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "food-write",
++ "events": [],
++ "implementedBy": [
++ "PUT /api/v1/pantry"
++ ]
++}
ddiff --git a/manifests/capabilities/food.plan.read.json b/manifests/capabilities/food.plan.read.json
nnew file mode 100644
iindex 00000000..aa6b9d05
---- /dev/null
++++ b/manifests/capabilities/food.plan.read.json
@@@ -0,0 +1,22 @@
++{
++ "id": "food.plan.read",
++ "version": "1.0",
++ "owner": "food",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read the plans a caller saved: GET /api/v1/plans (newest first, ?limit to 50) → { plans }, and GET /api/v1/plans/:id → one plan. Only the person who saved a plan (or their tokens) can read it.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "food.plan-result@1",
++ "permissions": [
++ "food:plans:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "food-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/plans",
++ "GET /api/v1/plans/:id"
++ ]
++}
ddiff --git a/manifests/capabilities/food.plan.write.json b/manifests/capabilities/food.plan.write.json
nnew file mode 100644
iindex 00000000..81e635a7
---- /dev/null
++++ b/manifests/capabilities/food.plan.write.json
@@@ -0,0 +1,22 @@
++{
++ "id": "food.plan.write",
++ "version": "1.0",
++ "owner": "food",
++ "status": "active",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "Plan a shop for a household and a budget and save it: POST /api/v1/plans with { people, days, budget?, title? }. Food picks budget items from its food list to cover each food group for every person and day, splits them by the cheapest store and lays out a menu; the saved plan (201, Location /api/v1/plans/:id) is the caller's own.",
++ "inputSchema": "food.plan-create-request@1",
++ "outputSchema": "food.plan-result@1",
++ "permissions": [
++ "food:plans:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "food-write",
++ "events": [],
++ "implementedBy": [
++ "POST /api/v1/plans"
++ ]
++}
ddiff --git a/manifests/products/openvibe.food.json b/manifests/products/openvibe.food.json
iindex 92d95c35..ca03055a 100644
---- a/manifests/products/openvibe.food.json
++++ b/manifests/products/openvibe.food.json
@@@ -27,18 +27,6 @@
"keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
"launch": "the food tools moving over from OpenVibe.Tools",
"relationships": {
-- "noRepo": true,
-- "meanwhile": [
-- {
-- "label": "Food tools",
-- "url": "https://food.openvibe.tools",
-- "note": "The food and grocery tools work today."
-- },
-- {
-- "label": "OpenVibe.Coupons",
-- "url": "https://openvibe.coupons",
-- "note": "Coupons are the sibling room."
-- }
-- ]
++ "service": "food"
}
}
ddiff --git a/manifests/products/openvibe.help.json b/manifests/products/openvibe.help.json
iindex c8418ca0..15b199e3 100644
---- a/manifests/products/openvibe.help.json
++++ b/manifests/products/openvibe.help.json
@@@ -27,18 +27,6 @@
"keywords": "openvibe help, openvibe support, help center, support tickets, troubleshooting",
"launch": "the ticket service and the Frame's Help button",
"relationships": {
-- "noRepo": true,
-- "meanwhile": [
-- {
-- "label": "Network status",
-- "url": "https://openvibe.network/status",
-- "note": "Service health and incidents today."
-- },
-- {
-- "label": "OpenVibe.Wiki",
-- "url": "https://openvibe.wiki",
-- "note": "Guides and concepts."
-- }
-- ]
++ "service": "help"
}
}
ddiff --git a/manifests/products/openvibe.work.json b/manifests/products/openvibe.work.json
iindex cb42054a..6c199de2 100644
---- a/manifests/products/openvibe.work.json
++++ b/manifests/products/openvibe.work.json
@@@ -27,13 +27,6 @@
"keywords": "openvibe work, job search, job listings, job alerts, ai job application assistant",
"launch": "job listings and alerts",
"relationships": {
-- "noRepo": true,
-- "meanwhile": [
-- {
-- "label": "OpenVibe.Network",
-- "url": "https://openvibe.network",
-- "note": "One free account for every OpenVibe site, ready today."
-- }
-- ]
++ "service": "work"
}
}
ddiff --git a/manifests/services/food.json b/manifests/services/food.json
nnew file mode 100644
iindex 00000000..8e926856
---- /dev/null
++++ b/manifests/services/food.json
@@@ -0,0 +1,84 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.116.0 <1.0.0"
++ },
++ "id": "food",
++ "name": "OpenVibe.Food",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.Food",
++ "domains": [
++ "openvibe.food"
++ ],
++ "publicOrigin": "https://openvibe.food",
++ "exposure": {
++ "state": "live",
++ "publicSite": "service"
++ },
++ "capabilities": [
++ "food.plan.read",
++ "food.plan.write",
++ "food.pantry.read",
++ "food.pantry.write"
++ ],
++ "eventsProduced": [],
++ "eventsConsumed": [],
++ "namespacesOwned": [
++ "food.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; it checks nothing else (the database is what ready checks)"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 10,
++ "drains": [
++ "HTTP server closed",
++ "Network key refresher stopped",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "sessions",
++ "what": "sign-in sessions are rows in the database, so a person stays signed in across the restart; nothing runs in the background that a restart could leave half done"
++ },
++ {
++ "kind": "state",
++ "what": "the OpenStreetMap answer cache is read from food_geo_cache, so a place searched before the stop is not fetched again"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "migrations 0001-0002 only create Food's own tables; an earlier release has none to read"
++ }
++ },
++ "contracts": {
++ "range": ">=0.116.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: one process serves the site"
++ }
++ },
++ "notes": "Port 4970, unit openvibe-food.service. Food near you (food banks and budget grocers from OpenStreetMap through Nominatim and Overpass, one request a second per host, cached 7 days, ODbL attribution on every answer), the budget food list carried over from OpenVibe.Tools with chain-by-chain prices, meal plans for a household and a budget (saved per person), and a pantry with deterministic \"what can I make\" suggestions. Public reads need no token; plans and pantries are a signed-in person's (or an app token's with the capability). Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client food, JWKS). Owns food_plans, food_pantry and food_geo_cache.",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:4970",
++ "site": {
++ "name": "Food",
++ "icon": "food",
++ "tagline": "What should we eat?",
++ "what": "Find food banks and budget grocers near you, plan a week of meals for your household and budget, and see what your cupboard can make.",
++ "legalProfile": "tools",
++ "position": 24
++ }
++}
ddiff --git a/manifests/services/help.json b/manifests/services/help.json
nnew file mode 100644
iindex 00000000..6be7184a
---- /dev/null
++++ b/manifests/services/help.json
@@@ -0,0 +1,75 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.116.0 <1.0.0"
++ },
++ "id": "help",
++ "name": "OpenVibe.Help",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.Help",
++ "domains": [
++ "openvibe.help"
++ ],
++ "publicOrigin": "https://openvibe.help",
++ "exposure": {
++ "state": "live",
++ "publicSite": "service"
++ },
++ "capabilities": [],
++ "eventsProduced": [],
++ "eventsConsumed": [],
++ "namespacesOwned": [
++ "help.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; it checks nothing else (the database is what ready checks)"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 10,
++ "drains": [
++ "HTTP server closed",
++ "Network key refresher stopped",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "sessions",
++ "what": "sign-in sessions are rows in the database, so a person stays signed in across the restart; nothing runs in the background that a restart could leave half done"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "migrations 0001-0002 only create Help's own tables; an earlier release has none to read"
++ }
++ },
++ "contracts": {
++ "range": ">=0.116.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: one process serves the site"
++ }
++ },
++ "notes": "Port 5020, unit openvibe-help.service. The network's help centre: a catalog of every OpenVibe product site built from openvibe-contracts (each site's state, one line, pillars and FAQ), articles generated from it, search across them, and support tickets a signed-in person opens and staff answer (staff judged by the Network role claim). Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client help, JWKS). Owns help_tickets and help_messages. No capabilities yet: tickets are a person's own, not an app's.",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:5020",
++ "site": {
++ "name": "Help",
++ "icon": "help",
++ "tagline": "Answers for every OpenVibe site",
++ "what": "Every OpenVibe site in one help centre: what each one does, its common questions, and a ticket to a person when the answer is not there.",
++ "legalProfile": "account",
++ "position": 25
++ }
++}
ddiff --git a/manifests/services/work.json b/manifests/services/work.json
nnew file mode 100644
iindex 00000000..6b6a8bf4
---- /dev/null
++++ b/manifests/services/work.json
@@@ -0,0 +1,76 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.116.0 <1.0.0"
++ },
++ "id": "work",
++ "name": "OpenVibe.Work",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.Work",
++ "domains": [
++ "openvibe.work"
++ ],
++ "publicOrigin": "https://openvibe.work",
++ "exposure": {
++ "state": "live",
++ "publicSite": "service"
++ },
++ "capabilities": [],
++ "eventsProduced": [],
++ "eventsConsumed": [],
++ "namespacesOwned": [
++ "work.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; it checks nothing else (the database is what ready checks)"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 10,
++ "drains": [
++ "HTTP server closed",
++ "ingest timer stopped (a fetch in flight is abandoned; its rows are written per listing)",
++ "Network key refresher stopped",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "jobs",
++ "what": "the ingest starts again on its timer; each source is fetched when its last fetch is older than 6 hours, so a fetch cut short by a restart is simply redone"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "migrations 0001-0002 only create Work's own tables; an earlier release has none to read"
++ }
++ },
++ "contracts": {
++ "range": ">=0.116.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: one process serves the site"
++ }
++ },
++ "notes": "Port 4960, unit openvibe-work.service. Job listings gathered from open job-board APIs (Arbeitnow, Remotive, RemoteOK) with their provenance: a short plain-text excerpt only, the source named and the original listing linked on every list and page, never the full description. Search with remote/type/location filters, JobPosting JSON-LD, and saved searches with a count of what is new since you last looked. The ingest runs every 6 hours per source when WORK_INGEST=on, upserts by (source, source_id) and expires listings not seen for 30 days. Consumes OpenVibe.Network (SSO with PKCE S256 as OAuth client work, JWKS). Owns work_listings, work_source_fetches and work_saved_searches. No capabilities yet: saved searches are a person's, listings are public.",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:4960",
++ "site": {
++ "name": "Work",
++ "icon": "work",
++ "tagline": "Find work, with an agent on your side",
++ "what": "Job listings from open job boards in one search, each linked back to where you apply, with saved searches that tell you what is new.",
++ "legalProfile": "info",
++ "position": 26
++ }
++}
ddiff --git a/package.json b/package.json
iindex 225807b5..d88d896f 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.115.0",
++ "version": "0.116.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",