openvibers/openvibe.contracts Commits

Contracts 0.147.0: work.application.manage and food.recipes.suggest (#103)

OP OpenVibers committed 12 Oct 2026 · cf1d47a99f · 61 files, +4252 −66

Two capabilities for features shipping now: Work's job-application tracker and Food's recipe ideas.

- **`work.application.manage`** (owner work) lets apps and agents manage a person's tracked job applications on OpenVibe.Work: list, add (from a listing or by hand), read with status history, edit, delete.
  - Schemas: `work.application@1`, `work.application-write-request@1`, `work.application-result@1`.
  - Asking the person's OpenVibe.Actor agent is their own act and takes no capability.
  - The work manifest lists the capability, and its notes name the new tables.
- **`food.recipes.suggest`** (owner food): recipe ideas from the caller's pantry through OpenVibe.AI's `food.suggest_recipes`, with what's left to buy priced from Food's own food list.
  - Schemas: `food.recipe-suggest-request@1`, `food.recipe-suggestion@1`, `food.recipe-result@1`.
- Each schema has valid and invalid fixtures. Generated files are refreshed, `compat.js v0.146.0` finds no breaking changes, and the suite passes (18,867 checks).

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 411f9684..7808fcee 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,15 @@ 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.147.0 — 2026-10-12
++
++- `work.application.manage` (owner work): a person's tracked job applications on OpenVibe.Work, for apps and agents
++ (`work.application@1`, `work.application-write-request@1`, `work.application-result@1`). Asking the person's
++ OpenVibe.Actor agent about one is the person's own act and takes no capability. The work manifest lists it.
++- `food.recipes.suggest` (owner food): recipe ideas from the caller's pantry through OpenVibe.AI's
++ `food.suggest_recipes`, priced from Food's own food list (`food.recipe-suggest-request@1`,
++ `food.recipe-suggestion@1`, `food.recipe-result@1`). The food manifest lists it.
++
## 0.146.0 — 2026-10-12
- `sources.source@1` and `sources.source-write-request@1`: an `api` endpoint may declare `where` (up to 10 path →
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 44c4d8ff..7ea0f7c1 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -6802,6 +6802,60 @@
"visibility": "first-party",
"compatibility": "backward",
"status": "active"
++ },
++ {
++ "id": "work.application",
++ "version": "1.0.0",
++ "owner": "work",
++ "schema": "work/application.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "work.application-write-request",
++ "version": "1.0.0",
++ "owner": "work",
++ "schema": "work/application-write-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "work.application-result",
++ "version": "1.0.0",
++ "owner": "work",
++ "schema": "work/application-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.recipe-suggest-request",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/recipe-suggest-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.recipe-suggestion",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/recipe-suggestion.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "food.recipe-result",
++ "version": "1.0.0",
++ "owner": "food",
++ "schema": "food/recipe-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
}
]
}
ddiff --git a/contracts/food/recipe-result.v1.json b/contracts/food/recipe-result.v1.json
nnew file mode 100644
iindex 00000000..2e927b5a
---- /dev/null
++++ b/contracts/food/recipe-result.v1.json
@@@ -0,0 +1,27 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/recipe-result.v1.json",
++ "title": "FoodRecipeResult",
++ "description": "food.recipe-result@1: what food.recipes.suggest answers: the new suggestion (POST /api/v1/recipes/suggest, 201) or the caller's recent ones (GET /api/v1/recipes).",
++ "anyOf": [
++ {
++ "$ref": "recipe-suggestion.v1.json"
++ },
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "recipes"
++ ],
++ "properties": {
++ "recipes": {
++ "type": "array",
++ "maxItems": 20,
++ "items": {
++ "$ref": "recipe-suggestion.v1.json"
++ }
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/food/recipe-suggest-request.v1.json b/contracts/food/recipe-suggest-request.v1.json
nnew file mode 100644
iindex 00000000..84d6edae
---- /dev/null
++++ b/contracts/food/recipe-suggest-request.v1.json
@@@ -0,0 +1,19 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/recipe-suggest-request.v1.json",
++ "title": "FoodRecipeSuggestRequest",
++ "description": "food.recipe-suggest-request@1: POST /api/v1/recipes/suggest. Asks OpenVibe.AI's food.suggest_recipes workflow for up to three recipes from the caller's saved pantry (an empty pantry is 422 food.pantry_empty). 10 a person a day (429 food.recipes_daily_limit); with Food's AI connection off, 503 food.ai_off.",
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "ask": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "people": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ }
++ }
++}
ddiff --git a/contracts/food/recipe-suggestion.v1.json b/contracts/food/recipe-suggestion.v1.json
nnew file mode 100644
iindex 00000000..1708f392
---- /dev/null
++++ b/contracts/food/recipe-suggestion.v1.json
@@@ -0,0 +1,176 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/recipe-suggestion.v1.json",
++ "title": "FoodRecipeSuggestion",
++ "description": "food.recipe-suggestion@1: one stored answer (the last 20 a person are kept). Ingredients are from the pantry or cite the food-list rows Food sent the workflow; to_buy and to_buy_cost are Food's own typical prices, never the model's. A recipe is AI-suggested and labelled so on the page.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "created_at",
++ "ask",
++ "people",
++ "ai_run_id",
++ "recipes"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^rcp_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ask": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "people": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 12
++ },
++ "ai_run_id": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "recipes": {
++ "type": "array",
++ "maxItems": 3,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "title",
++ "ingredients",
++ "steps",
++ "to_buy",
++ "to_buy_cost",
++ "cost_note"
++ ],
++ "properties": {
++ "title": {
++ "type": "string",
++ "maxLength": 160
++ },
++ "serves": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 12
++ },
++ "minutes": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 600
++ },
++ "ingredients": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 25,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "name",
++ "amount",
++ "from_pantry",
++ "citations"
++ ],
++ "properties": {
++ "name": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "amount": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 80
++ },
++ "from_pantry": {
++ "type": "boolean"
++ },
++ "citations": {
++ "type": "array",
++ "items": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ },
++ "steps": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 20,
++ "items": {
++ "type": "string",
++ "maxLength": 600
++ }
++ },
++ "notes": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 600
++ },
++ "to_buy": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "slug",
++ "name",
++ "price"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "price": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "to_buy_cost": {
++ "type": "number",
++ "minimum": 0
++ },
++ "to_buy_cost_partial": {
++ "type": "boolean"
++ },
++ "cost_note": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/work/application-result.v1.json b/contracts/work/application-result.v1.json
nnew file mode 100644
iindex 00000000..6f7ee593
---- /dev/null
++++ b/contracts/work/application-result.v1.json
@@@ -0,0 +1,39 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/work/application-result.v1.json",
++ "title": "WorkApplicationResult",
++ "description": "work.application-result@1: what work.application.manage answers: one application, or a page of them (GET /api/v1/me/applications, newest first, ?status= and ?cursor=; `next` is the next page's cursor or null). DELETE answers 204 with no body.",
++ "anyOf": [
++ {
++ "$ref": "application.v1.json"
++ },
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "applications",
++ "next",
++ "count"
++ ],
++ "properties": {
++ "applications": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "application.v1.json"
++ }
++ },
++ "next": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/work/application-write-request.v1.json b/contracts/work/application-write-request.v1.json
nnew file mode 100644
iindex 00000000..dd32f0ab
---- /dev/null
++++ b/contracts/work/application-write-request.v1.json
@@@ -0,0 +1,63 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/work/application-write-request.v1.json",
++ "title": "WorkApplicationWriteRequest",
++ "description": "work.application-write-request@1: bodies of work.application.manage. POST /api/v1/me/applications takes a listing_id (title, company and URL are copied from the listing; a second POST for the same listing answers the existing application with 200) or title + company + url by hand. PATCH /api/v1/me/applications/:id takes status, notes, applied_at, next_step and next_step_at; a status change appends an event, and status applied with no applied_at stamps it now. Unknown fields are 400.",
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "listing_id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ },
++ "company": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ },
++ "status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "notes": {
++ "type": "string",
++ "maxLength": 5000
++ },
++ "applied_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "next_step": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "next_step_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/work/application.v1.json b/contracts/work/application.v1.json
nnew file mode 100644
iindex 00000000..8b2d7917
---- /dev/null
++++ b/contracts/work/application.v1.json
@@@ -0,0 +1,148 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/work/application.v1.json",
++ "title": "WorkApplication",
++ "description": "work.application@1: a person's tracked job application on OpenVibe.Work (server/applications.js toWire), as GET/POST/PATCH /api/v1/me/applications[/:id] answer it. GET of one application adds its append-only status changes as `events`.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "subject_id",
++ "listing_id",
++ "title",
++ "company",
++ "url",
++ "status",
++ "notes",
++ "applied_at",
++ "next_step",
++ "next_step_at",
++ "actor_task_id",
++ "assists",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^wap_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject_id": {
++ "type": "string",
++ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "listing_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The Work listing it was tracked from, or null when added by hand."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ },
++ "company": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ },
++ "status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "notes": {
++ "type": "string",
++ "maxLength": 5000
++ },
++ "applied_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "next_step": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "next_step_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "actor_task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The latest OpenVibe.Actor task asked for this application (POST …/assist)."
++ },
++ "assists": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "events": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "at",
++ "from_status",
++ "to_status"
++ ],
++ "properties": {
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "from_status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "to_status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/food.recipe-result/invalid/bare-array.json b/fixtures/food.recipe-result/invalid/bare-array.json
nnew file mode 100644
iindex 00000000..a45c4f4c
---- /dev/null
++++ b/fixtures/food.recipe-result/invalid/bare-array.json
@@@ -0,0 +1,47 @@
++[
++ {
++ "id": "rcp_01JQ8Z0000000000000000000C",
++ "created_at": "2026-10-12T05:25:14.000Z",
++ "ask": "something quick",
++ "people": 2,
++ "ai_run_id": "run_01JQ8Z0000000000000000000D",
++ "recipes": [
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ }
++ ]
++ }
++]
ddiff --git a/fixtures/food.recipe-result/valid/list.json b/fixtures/food.recipe-result/valid/list.json
nnew file mode 100644
iindex 00000000..d5862aed
---- /dev/null
++++ b/fixtures/food.recipe-result/valid/list.json
@@@ -0,0 +1,49 @@
++{
++ "recipes": [
++ {
++ "id": "rcp_01JQ8Z0000000000000000000C",
++ "created_at": "2026-10-12T05:25:14.000Z",
++ "ask": "something quick",
++ "people": 2,
++ "ai_run_id": "run_01JQ8Z0000000000000000000D",
++ "recipes": [
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/fixtures/food.recipe-suggest-request/invalid/too-many-people.json b/fixtures/food.recipe-suggest-request/invalid/too-many-people.json
nnew file mode 100644
iindex 00000000..eb5f8b86
---- /dev/null
++++ b/fixtures/food.recipe-suggest-request/invalid/too-many-people.json
@@@ -0,0 +1,3 @@
++{
++ "people": 40
++}
ddiff --git a/fixtures/food.recipe-suggest-request/valid/ask.json b/fixtures/food.recipe-suggest-request/valid/ask.json
nnew file mode 100644
iindex 00000000..a0e5c4a4
---- /dev/null
++++ b/fixtures/food.recipe-suggest-request/valid/ask.json
@@@ -0,0 +1,4 @@
++{
++ "ask": "something quick and filling",
++ "people": 2
++}
ddiff --git a/fixtures/food.recipe-suggest-request/valid/empty.json b/fixtures/food.recipe-suggest-request/valid/empty.json
nnew file mode 100644
iindex 00000000..0967ef42
---- /dev/null
++++ b/fixtures/food.recipe-suggest-request/valid/empty.json
@@@ -0,0 +1 @@
++{}
ddiff --git a/fixtures/food.recipe-suggestion/invalid/four-recipes.json b/fixtures/food.recipe-suggestion/invalid/four-recipes.json
nnew file mode 100644
iindex 00000000..b5d7a19d
---- /dev/null
++++ b/fixtures/food.recipe-suggestion/invalid/four-recipes.json
@@@ -0,0 +1,153 @@
++{
++ "id": "rcp_01JQ8Z0000000000000000000C",
++ "created_at": "2026-10-12T05:25:14.000Z",
++ "ask": "something quick",
++ "people": 2,
++ "ai_run_id": "run_01JQ8Z0000000000000000000D",
++ "recipes": [
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ },
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ },
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ },
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ }
++ ]
++}
ddiff --git a/fixtures/food.recipe-suggestion/valid/one.json b/fixtures/food.recipe-suggestion/valid/one.json
nnew file mode 100644
iindex 00000000..e3957aaa
---- /dev/null
++++ b/fixtures/food.recipe-suggestion/valid/one.json
@@@ -0,0 +1,45 @@
++{
++ "id": "rcp_01JQ8Z0000000000000000000C",
++ "created_at": "2026-10-12T05:25:14.000Z",
++ "ask": "something quick",
++ "people": 2,
++ "ai_run_id": "run_01JQ8Z0000000000000000000D",
++ "recipes": [
++ {
++ "title": "Egg fried rice",
++ "serves": 2,
++ "minutes": 15,
++ "ingredients": [
++ {
++ "name": "Eggs",
++ "amount": "3",
++ "from_pantry": true,
++ "citations": []
++ },
++ {
++ "name": "White rice",
++ "amount": "2 cups cooked",
++ "from_pantry": false,
++ "citations": [
++ 1
++ ]
++ }
++ ],
++ "steps": [
++ "Scramble the eggs and set them aside.",
++ "Fry the rice, stir the eggs back in and serve."
++ ],
++ "notes": null,
++ "to_buy": [
++ {
++ "slug": "rice-long-grain",
++ "name": "Long Grain White Rice (2 lb)",
++ "price": 1.25
++ }
++ ],
++ "to_buy_cost": 1.25,
++ "to_buy_cost_partial": false,
++ "cost_note": "Cost is an estimate from typical prices, not a live price."
++ }
++ ]
++}
ddiff --git a/fixtures/work.application-result/invalid/missing-count.json b/fixtures/work.application-result/invalid/missing-count.json
nnew file mode 100644
iindex 00000000..2c1c13e4
---- /dev/null
++++ b/fixtures/work.application-result/invalid/missing-count.json
@@@ -0,0 +1,22 @@
++{
++ "applications": [
++ {
++ "id": "wap_01JQ8Z0000000000000000000A",
++ "subject_id": "user:usr_01JQ8Z0000000000000000000B",
++ "listing_id": null,
++ "title": "Backend Engineer",
++ "company": "Northwind",
++ "url": "https://northwind.example/jobs/1",
++ "status": "applied",
++ "notes": "",
++ "applied_at": "2026-10-12T05:00:00.000Z",
++ "next_step": "",
++ "next_step_at": null,
++ "actor_task_id": null,
++ "assists": 0,
++ "created_at": "2026-10-12T05:00:00.000Z",
++ "updated_at": "2026-10-12T05:00:00.000Z"
++ }
++ ],
++ "next": null
++}
ddiff --git a/fixtures/work.application-result/valid/page.json b/fixtures/work.application-result/valid/page.json
nnew file mode 100644
iindex 00000000..5e5ae7fc
---- /dev/null
++++ b/fixtures/work.application-result/valid/page.json
@@@ -0,0 +1,23 @@
++{
++ "applications": [
++ {
++ "id": "wap_01JQ8Z0000000000000000000A",
++ "subject_id": "user:usr_01JQ8Z0000000000000000000B",
++ "listing_id": null,
++ "title": "Backend Engineer",
++ "company": "Northwind",
++ "url": "https://northwind.example/jobs/1",
++ "status": "applied",
++ "notes": "",
++ "applied_at": "2026-10-12T05:00:00.000Z",
++ "next_step": "",
++ "next_step_at": null,
++ "actor_task_id": null,
++ "assists": 0,
++ "created_at": "2026-10-12T05:00:00.000Z",
++ "updated_at": "2026-10-12T05:00:00.000Z"
++ }
++ ],
++ "next": null,
++ "count": 1
++}
ddiff --git a/fixtures/work.application-write-request/invalid/unknown-field.json b/fixtures/work.application-write-request/invalid/unknown-field.json
nnew file mode 100644
iindex 00000000..b67dc65f
---- /dev/null
++++ b/fixtures/work.application-write-request/invalid/unknown-field.json
@@@ -0,0 +1,3 @@
++{
++ "salary": 100000
++}
ddiff --git a/fixtures/work.application-write-request/valid/from-listing.json b/fixtures/work.application-write-request/valid/from-listing.json
nnew file mode 100644
iindex 00000000..8c2c330b
---- /dev/null
++++ b/fixtures/work.application-write-request/valid/from-listing.json
@@@ -0,0 +1,3 @@
++{
++ "listing_id": "job_arbeitnow_backend-engineer-1"
++}
ddiff --git a/fixtures/work.application-write-request/valid/patch.json b/fixtures/work.application-write-request/valid/patch.json
nnew file mode 100644
iindex 00000000..b5ddf6e3
---- /dev/null
++++ b/fixtures/work.application-write-request/valid/patch.json
@@@ -0,0 +1,5 @@
++{
++ "status": "interviewing",
++ "next_step": "Second interview",
++ "next_step_at": "2026-10-16T17:00:00.000Z"
++}
ddiff --git a/fixtures/work.application/invalid/bad-status.json b/fixtures/work.application/invalid/bad-status.json
nnew file mode 100644
iindex 00000000..f4625fcf
---- /dev/null
++++ b/fixtures/work.application/invalid/bad-status.json
@@@ -0,0 +1,17 @@
++{
++ "id": "wap_01JQ8Z0000000000000000000A",
++ "subject_id": "user:usr_01JQ8Z0000000000000000000B",
++ "listing_id": null,
++ "title": "Backend Engineer",
++ "company": "Northwind",
++ "url": "https://northwind.example/jobs/1",
++ "status": "ghosted",
++ "notes": "",
++ "applied_at": "2026-10-12T05:00:00.000Z",
++ "next_step": "",
++ "next_step_at": null,
++ "actor_task_id": null,
++ "assists": 0,
++ "created_at": "2026-10-12T05:00:00.000Z",
++ "updated_at": "2026-10-12T05:00:00.000Z"
++}
ddiff --git a/fixtures/work.application/valid/applied.json b/fixtures/work.application/valid/applied.json
nnew file mode 100644
iindex 00000000..f92498f6
---- /dev/null
++++ b/fixtures/work.application/valid/applied.json
@@@ -0,0 +1,17 @@
++{
++ "id": "wap_01JQ8Z0000000000000000000A",
++ "subject_id": "user:usr_01JQ8Z0000000000000000000B",
++ "listing_id": null,
++ "title": "Backend Engineer",
++ "company": "Northwind",
++ "url": "https://northwind.example/jobs/1",
++ "status": "applied",
++ "notes": "",
++ "applied_at": "2026-10-12T05:00:00.000Z",
++ "next_step": "",
++ "next_step_at": null,
++ "actor_task_id": null,
++ "assists": 0,
++ "created_at": "2026-10-12T05:00:00.000Z",
++ "updated_at": "2026-10-12T05:00:00.000Z"
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 5f57817a..b69dcec1 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -76499,6 +76499,508 @@
}
}
}
++ },
++ "work.application": {
++ "version": "1.0.0",
++ "owner": "work",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/work/application.v1.json",
++ "title": "WorkApplication",
++ "description": "work.application@1: a person's tracked job application on OpenVibe.Work (server/applications.js toWire), as GET/POST/PATCH /api/v1/me/applications[/:id] answer it. GET of one application adds its append-only status changes as `events`.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "subject_id",
++ "listing_id",
++ "title",
++ "company",
++ "url",
++ "status",
++ "notes",
++ "applied_at",
++ "next_step",
++ "next_step_at",
++ "actor_task_id",
++ "assists",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^wap_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject_id": {
++ "type": "string",
++ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "listing_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The Work listing it was tracked from, or null when added by hand."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ },
++ "company": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ },
++ "status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "notes": {
++ "type": "string",
++ "maxLength": 5000
++ },
++ "applied_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "next_step": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "next_step_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "actor_task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The latest OpenVibe.Actor task asked for this application (POST …/assist)."
++ },
++ "assists": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "events": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "at",
++ "from_status",
++ "to_status"
++ ],
++ "properties": {
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "from_status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "to_status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "work.application-write-request": {
++ "version": "1.0.0",
++ "owner": "work",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/work/application-write-request.v1.json",
++ "title": "WorkApplicationWriteRequest",
++ "description": "work.application-write-request@1: bodies of work.application.manage. POST /api/v1/me/applications takes a listing_id (title, company and URL are copied from the listing; a second POST for the same listing answers the existing application with 200) or title + company + url by hand. PATCH /api/v1/me/applications/:id takes status, notes, applied_at, next_step and next_step_at; a status change appends an event, and status applied with no applied_at stamps it now. Unknown fields are 400.",
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "listing_id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ },
++ "company": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ },
++ "status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "notes": {
++ "type": "string",
++ "maxLength": 5000
++ },
++ "applied_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "next_step": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "next_step_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "work.application-result": {
++ "version": "1.0.0",
++ "owner": "work",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/work/application-result.v1.json",
++ "title": "WorkApplicationResult",
++ "description": "work.application-result@1: what work.application.manage answers: one application, or a page of them (GET /api/v1/me/applications, newest first, ?status= and ?cursor=; `next` is the next page's cursor or null). DELETE answers 204 with no body.",
++ "anyOf": [
++ {
++ "$ref": "application.v1.json"
++ },
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "applications",
++ "next",
++ "count"
++ ],
++ "properties": {
++ "applications": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "application.v1.json"
++ }
++ },
++ "next": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++ }
++ },
++ "food.recipe-suggest-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/recipe-suggest-request.v1.json",
++ "title": "FoodRecipeSuggestRequest",
++ "description": "food.recipe-suggest-request@1: POST /api/v1/recipes/suggest. Asks OpenVibe.AI's food.suggest_recipes workflow for up to three recipes from the caller's saved pantry (an empty pantry is 422 food.pantry_empty). 10 a person a day (429 food.recipes_daily_limit); with Food's AI connection off, 503 food.ai_off.",
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "ask": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "people": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ }
++ }
++ }
++ },
++ "food.recipe-suggestion": {
++ "version": "1.0.0",
++ "owner": "food",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/food/recipe-suggestion.v1.json",
++ "title": "FoodRecipeSuggestion",
++ "description": "food.recipe-suggestion@1: one stored answer (the last 20 a person are kept). Ingredients are from the pantry or cite the food-list rows Food sent the workflow; to_buy and to_buy_cost are Food's own typical prices, never the model's. A recipe is AI-suggested and labelled so on the page.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "created_at",
++ "ask",
++ "people",
++ "ai_run_id",
++ "recipes"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^rcp_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ask": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "people": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 12
++ },
++ "ai_run_id": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "recipes": {
++ "type": "array",
++ "maxItems": 3,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "title",
++ "ingredients",
++ "steps",
++ "to_buy",
++ "to_buy_cost",
++ "cost_note"
++ ],
++ "properties": {
++ "title": {
++ "type": "string",
++ "maxLength": 160
++ },
++ "serves": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 12
++ },
++ "minutes": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 600
++ },
++ "ingredients": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 25,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "name",
++ "amount",
++ "from_pantry",
++ "citations"
++ ],
++ "properties": {
++ "name": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "amount": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 80
++ },
++ "from_pantry": {
++ "type": "boolean"
++ },
++ "citations": {
++ "type": "array",
++ "items": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ },
++ "steps": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 20,
++ "items": {
++ "type": "string",
++ "maxLength": 600
++ }
++ },
++ "notes": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 600
++ },
++ "to_buy": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "slug",
++ "name",
++ "price"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "price": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "to_buy_cost": {
++ "type": "number",
++ "minimum": 0
++ },
++ "to_buy_cost_partial": {
++ "type": "boolean"
++ },
++ "cost_note": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "food.recipe-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/recipe-result.v1.json",
++ "title": "FoodRecipeResult",
++ "description": "food.recipe-result@1: what food.recipes.suggest answers: the new suggestion (POST /api/v1/recipes/suggest, 201) or the caller's recent ones (GET /api/v1/recipes).",
++ "anyOf": [
++ {
++ "$ref": "recipe-suggestion.v1.json"
++ },
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "recipes"
++ ],
++ "properties": {
++ "recipes": {
++ "type": "array",
++ "maxItems": 20,
++ "items": {
++ "$ref": "recipe-suggestion.v1.json"
++ }
++ }
++ }
++ }
++ ]
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 8c04a075..f7040862 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.146.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 8b377d1a..a95d29f9 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.146.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 a81774f2..ce4fb42f 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.146.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 dd982950..aa5fe026 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.146.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 49275234..b229853a 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.146.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 0ade0506..42041e82 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.146.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 7ccf3ae6..a0a17b61 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.146.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 064ebb03..186476c0 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.146.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 061fe182..f08f1b0c 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.146.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 135e1387..8c025dd8 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.146.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.147.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
iindex 776a6c91..82b5ed91 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.146.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.147.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"
@@@ -271,6 +271,132 @@
}
}
}
++ },
++ "/api/v1/recipes": {
++ "get": {
++ "operationId": "get_api_v1_recipes",
++ "summary": "Recipe ideas from the caller's saved pantry: POST /api/v1/recipes/suggest runs OpenVibe.AI's food.suggest_recipes workflow for the caller (up to three recipes; ingredients from the pantry or cited to ",
++ "description": "**food.recipes.suggest** (public): Recipe ideas from the caller's saved pantry: POST /api/v1/recipes/suggest runs OpenVibe.AI's food.suggest_recipes workflow for the caller (up to three recipes; ingredients from the pantry or cited to Food's food list; what is still to buy priced from Food's own typical prices), 10 a person a day; GET /api/v1/recipes lists the last 20. A person acting for themself needs no capability; an app or agent token needs this one.",
++ "tags": [
++ "food.recipes"
++ ],
++ "x-openvibe-capabilities": [
++ "food.recipes.suggest"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.recipes.suggest"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/food.recipe-suggest-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "ask",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 500
++ }
++ },
++ {
++ "name": "people",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.recipe-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/recipes/suggest": {
++ "post": {
++ "operationId": "post_api_v1_recipes_suggest",
++ "summary": "Recipe ideas from the caller's saved pantry: POST /api/v1/recipes/suggest runs OpenVibe.AI's food.suggest_recipes workflow for the caller (up to three recipes; ingredients from the pantry or cited to ",
++ "description": "**food.recipes.suggest** (public): Recipe ideas from the caller's saved pantry: POST /api/v1/recipes/suggest runs OpenVibe.AI's food.suggest_recipes workflow for the caller (up to three recipes; ingredients from the pantry or cited to Food's food list; what is still to buy priced from Food's own typical prices), 10 a person a day; GET /api/v1/recipes lists the last 20. A person acting for themself needs no capability; an app or agent token needs this one.",
++ "tags": [
++ "food.recipes"
++ ],
++ "x-openvibe-capabilities": [
++ "food.recipes.suggest"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "food.recipes.suggest"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.recipe-suggest-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/food.recipe-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -642,6 +768,222 @@
}
}
}
++ },
++ "food.recipe-result.v1": {
++ "title": "FoodRecipeResult",
++ "description": "food.recipe-result@1: what food.recipes.suggest answers: the new suggestion (POST /api/v1/recipes/suggest, 201) or the caller's recent ones (GET /api/v1/recipes).",
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/food.recipe-suggestion.v1"
++ },
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "recipes"
++ ],
++ "properties": {
++ "recipes": {
++ "type": "array",
++ "maxItems": 20,
++ "items": {
++ "$ref": "#/components/schemas/food.recipe-suggestion.v1"
++ }
++ }
++ }
++ }
++ ]
++ },
++ "food.recipe-suggest-request.v1": {
++ "title": "FoodRecipeSuggestRequest",
++ "description": "food.recipe-suggest-request@1: POST /api/v1/recipes/suggest. Asks OpenVibe.AI's food.suggest_recipes workflow for up to three recipes from the caller's saved pantry (an empty pantry is 422 food.pantry_empty). 10 a person a day (429 food.recipes_daily_limit); with Food's AI connection off, 503 food.ai_off.",
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "ask": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "people": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 12
++ }
++ }
++ },
++ "food.recipe-suggestion.v1": {
++ "title": "FoodRecipeSuggestion",
++ "description": "food.recipe-suggestion@1: one stored answer (the last 20 a person are kept). Ingredients are from the pantry or cite the food-list rows Food sent the workflow; to_buy and to_buy_cost are Food's own typical prices, never the model's. A recipe is AI-suggested and labelled so on the page.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "created_at",
++ "ask",
++ "people",
++ "ai_run_id",
++ "recipes"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^rcp_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ask": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "people": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 12
++ },
++ "ai_run_id": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "recipes": {
++ "type": "array",
++ "maxItems": 3,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "title",
++ "ingredients",
++ "steps",
++ "to_buy",
++ "to_buy_cost",
++ "cost_note"
++ ],
++ "properties": {
++ "title": {
++ "type": "string",
++ "maxLength": 160
++ },
++ "serves": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 12
++ },
++ "minutes": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "maximum": 600
++ },
++ "ingredients": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 25,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "name",
++ "amount",
++ "from_pantry",
++ "citations"
++ ],
++ "properties": {
++ "name": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "amount": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 80
++ },
++ "from_pantry": {
++ "type": "boolean"
++ },
++ "citations": {
++ "type": "array",
++ "items": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ },
++ "steps": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 20,
++ "items": {
++ "type": "string",
++ "maxLength": 600
++ }
++ },
++ "notes": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 600
++ },
++ "to_buy": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "slug",
++ "name",
++ "price"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "price": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "to_buy_cost": {
++ "type": "number",
++ "minimum": 0
++ },
++ "to_buy_cost_partial": {
++ "type": "boolean"
++ },
++ "cost_note": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
}
},
"securitySchemes": {
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 4cf62d74..70d5fa52 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.146.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 e6502dee..2d77012c 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.146.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 3871d300..adc35096 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -75,8 +75,8 @@
"service": "food",
"name": "OpenVibe.Food",
"origin": "https://openvibe.food",
-- "operations": 5,
-- "capabilities": 4
++ "operations": 7,
++ "capabilities": 5
},
{
"service": "games",
@@@ -210,6 +210,13 @@
"origin": "https://openvibe.wiki",
"operations": 25,
"capabilities": 8
++ },
++ {
++ "service": "work",
++ "name": "OpenVibe.Work",
++ "origin": "https://openvibe.work",
++ "operations": 5,
++ "capabilities": 1
}
]
}
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 771e9e52..5b7743b0 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.146.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.147.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/live.json b/generated/openapi/live.json
iindex 3c1db4b7..e129e036 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.146.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 7abc572a..480691de 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.146.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 ff99526d..86e74ae7 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.146.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 e4d024d4..11cdfd68 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.146.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 e014984c..44a8f03c 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": "OpenRestream API",
-- "version": "0.146.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 1aadbdb9..9b45d323 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.146.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 696e015c..da49a392 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.146.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 97c472f5..e7288843 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.146.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 9a02c81a..8e673e6f 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.146.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 69551aeb..0b095f67 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.146.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 4f38ec4d..625b8dd4 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.146.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 b2bcfb25..92d6b7a4 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.146.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 4920c210..1f9bcd38 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.146.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 bbeddbf0..fb89e019 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.146.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 8dfe162d..af6be19c 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.146.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.147.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 36f1311f..876626a3 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.146.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.146.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.147.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.147.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/work.json b/generated/openapi/work.json
nnew file mode 100644
iindex 00000000..3bb78f67
---- /dev/null
++++ b/generated/openapi/work.json
@@@ -0,0 +1,918 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Work API",
++ "version": "0.147.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.147.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.work"
++ }
++ ],
++ "paths": {
++ "/api/v1/me/applications": {
++ "get": {
++ "operationId": "get_api_v1_me_applications",
++ "summary": "Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and nex",
++ "description": "**work.application.manage** (public): Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and next step (PATCH), and delete (DELETE). A person acting for themself needs no capability; an app or agent token needs this one. Asking the person's OpenVibe.Actor agent (POST …/assist) is the person's own act and takes no capability.",
++ "tags": [
++ "work.application"
++ ],
++ "x-openvibe-capabilities": [
++ "work.application.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.application.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/work.application-write-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "listing_id",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "title",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ }
++ },
++ {
++ "name": "company",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "url",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ }
++ },
++ {
++ "name": "status",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ }
++ },
++ {
++ "name": "notes",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 5000
++ }
++ },
++ {
++ "name": "applied_at",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ },
++ {
++ "name": "next_step",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "next_step_at",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-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_me_applications",
++ "summary": "Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and nex",
++ "description": "**work.application.manage** (public): Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and next step (PATCH), and delete (DELETE). A person acting for themself needs no capability; an app or agent token needs this one. Asking the person's OpenVibe.Actor agent (POST …/assist) is the person's own act and takes no capability.",
++ "tags": [
++ "work.application"
++ ],
++ "x-openvibe-capabilities": [
++ "work.application.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.application.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-write-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/me/applications/{id}": {
++ "delete": {
++ "operationId": "delete_api_v1_me_applications_id",
++ "summary": "Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and nex",
++ "description": "**work.application.manage** (public): Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and next step (PATCH), and delete (DELETE). A person acting for themself needs no capability; an app or agent token needs this one. Asking the person's OpenVibe.Actor agent (POST …/assist) is the person's own act and takes no capability.",
++ "tags": [
++ "work.application"
++ ],
++ "x-openvibe-capabilities": [
++ "work.application.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.application.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/work.application-write-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "listing_id",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "title",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ }
++ },
++ {
++ "name": "company",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "url",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ }
++ },
++ {
++ "name": "status",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ }
++ },
++ {
++ "name": "notes",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 5000
++ }
++ },
++ {
++ "name": "applied_at",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ },
++ {
++ "name": "next_step",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "next_step_at",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "get": {
++ "operationId": "get_api_v1_me_applications_id",
++ "summary": "Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and nex",
++ "description": "**work.application.manage** (public): Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and next step (PATCH), and delete (DELETE). A person acting for themself needs no capability; an app or agent token needs this one. Asking the person's OpenVibe.Actor agent (POST …/assist) is the person's own act and takes no capability.",
++ "tags": [
++ "work.application"
++ ],
++ "x-openvibe-capabilities": [
++ "work.application.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.application.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/work.application-write-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "listing_id",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "title",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ }
++ },
++ {
++ "name": "company",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "url",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ }
++ },
++ {
++ "name": "status",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ }
++ },
++ {
++ "name": "notes",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 5000
++ }
++ },
++ {
++ "name": "applied_at",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ },
++ {
++ "name": "next_step",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 200
++ }
++ },
++ {
++ "name": "next_step_at",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "patch": {
++ "operationId": "patch_api_v1_me_applications_id",
++ "summary": "Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and nex",
++ "description": "**work.application.manage** (public): Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and next step (PATCH), and delete (DELETE). A person acting for themself needs no capability; an app or agent token needs this one. Asking the person's OpenVibe.Actor agent (POST …/assist) is the person's own act and takes no capability.",
++ "tags": [
++ "work.application"
++ ],
++ "x-openvibe-capabilities": [
++ "work.application.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.application.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-write-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/work.application-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}."
++ }
++ }
++ },
++ "work.application-result.v1": {
++ "title": "WorkApplicationResult",
++ "description": "work.application-result@1: what work.application.manage answers: one application, or a page of them (GET /api/v1/me/applications, newest first, ?status= and ?cursor=; `next` is the next page's cursor or null). DELETE answers 204 with no body.",
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/work.application.v1"
++ },
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "applications",
++ "next",
++ "count"
++ ],
++ "properties": {
++ "applications": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "#/components/schemas/work.application.v1"
++ }
++ },
++ "next": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++ },
++ "work.application-write-request.v1": {
++ "title": "WorkApplicationWriteRequest",
++ "description": "work.application-write-request@1: bodies of work.application.manage. POST /api/v1/me/applications takes a listing_id (title, company and URL are copied from the listing; a second POST for the same listing answers the existing application with 200) or title + company + url by hand. PATCH /api/v1/me/applications/:id takes status, notes, applied_at, next_step and next_step_at; a status change appends an event, and status applied with no applied_at stamps it now. Unknown fields are 400.",
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "listing_id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ },
++ "company": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ },
++ "status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "notes": {
++ "type": "string",
++ "maxLength": 5000
++ },
++ "applied_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "next_step": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "next_step_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ }
++ },
++ "work.application.v1": {
++ "title": "WorkApplication",
++ "description": "work.application@1: a person's tracked job application on OpenVibe.Work (server/applications.js toWire), as GET/POST/PATCH /api/v1/me/applications[/:id] answer it. GET of one application adds its append-only status changes as `events`.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "subject_id",
++ "listing_id",
++ "title",
++ "company",
++ "url",
++ "status",
++ "notes",
++ "applied_at",
++ "next_step",
++ "next_step_at",
++ "actor_task_id",
++ "assists",
++ "created_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^wap_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject_id": {
++ "type": "string",
++ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "listing_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The Work listing it was tracked from, or null when added by hand."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ },
++ "company": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "maxLength": 2000
++ },
++ "status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "notes": {
++ "type": "string",
++ "maxLength": 5000
++ },
++ "applied_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "next_step": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "next_step_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "actor_task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The latest OpenVibe.Actor task asked for this application (POST …/assist)."
++ },
++ "assists": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "events": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "at",
++ "from_status",
++ "to_status"
++ ],
++ "properties": {
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "from_status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ },
++ "to_status": {
++ "enum": [
++ "saved",
++ "applied",
++ "interviewing",
++ "offer",
++ "rejected",
++ "withdrawn"
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "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/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 578d3335..afd44f2c 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -171633,3 +171633,1441 @@ export interface QuestIndexDocumentDeletedPayload {
*/
revision: number;
}
++
++/** [email protected] (owner: work) */
++/**
++ * work.application@1: a person's tracked job application on OpenVibe.Work (server/applications.js toWire), as GET/POST/PATCH /api/v1/me/applications[/:id] answer it. GET of one application adds its append-only status changes as `events`.
++ */
++export interface WorkApplication {
++ id: string;
++ subject_id: string;
++ /**
++ * The Work listing it was tracked from, or null when added by hand.
++ */
++ listing_id: string | null;
++ title: string;
++ company: string;
++ url: string;
++ status: "saved" | "applied" | "interviewing" | "offer" | "rejected" | "withdrawn";
++ notes: string;
++ applied_at: string | null;
++ next_step: string;
++ next_step_at: string | null;
++ /**
++ * The latest OpenVibe.Actor task asked for this application (POST …/assist).
++ */
++ actor_task_id: string | null;
++ assists: number;
++ created_at: string;
++ updated_at: string;
++ events?: {
++ at: string;
++ from_status: "saved" | "applied" | "interviewing" | "offer" | "rejected" | "withdrawn";
++ to_status: "saved" | "applied" | "interviewing" | "offer" | "rejected" | "withdrawn";
++ }[];
++}
++
++/** [email protected] (owner: work) */
++/**
++ * work.application-write-request@1: bodies of work.application.manage. POST /api/v1/me/applications takes a listing_id (title, company and URL are copied from the listing; a second POST for the same listing answers the existing application with 200) or title + company + url by hand. PATCH /api/v1/me/applications/:id takes status, notes, applied_at, next_step and next_step_at; a status change appends an event, and status applied with no applied_at stamps it now. Unknown fields are 400.
++ */
++export interface WorkApplicationWriteRequest {
++ listing_id?: string;
++ title?: string;
++ company?: string;
++ url?: string;
++ status?: "saved" | "applied" | "interviewing" | "offer" | "rejected" | "withdrawn";
++ notes?: string;
++ applied_at?: string | null;
++ next_step?: string;
++ next_step_at?: string | null;
++}
++
++/** [email protected] (owner: work) */
++/**
++ * work.application-result@1: what work.application.manage answers: one application, or a page of them (GET /api/v1/me/applications, newest first, ?status= and ?cursor=; `next` is the next page's cursor or null). DELETE answers 204 with no body.
++ */
++export type WorkApplicationResult =
++ | WorkApplication
++ | {
++ /**
++ * @maxItems 100
++ */
++ applications: WorkApplication[];
++ next: string | null;
++ count: number;
++ };
++
++/** [email protected] (owner: food) */
++/**
++ * food.recipe-suggest-request@1: POST /api/v1/recipes/suggest. Asks OpenVibe.AI's food.suggest_recipes workflow for up to three recipes from the caller's saved pantry (an empty pantry is 422 food.pantry_empty). 10 a person a day (429 food.recipes_daily_limit); with Food's AI connection off, 503 food.ai_off.
++ */
++export interface FoodRecipeSuggestRequest {
++ ask?: string;
++ people?: number;
++}
++
++/** [email protected] (owner: food) */
++/**
++ * food.recipe-suggestion@1: one stored answer (the last 20 a person are kept). Ingredients are from the pantry or cite the food-list rows Food sent the workflow; to_buy and to_buy_cost are Food's own typical prices, never the model's. A recipe is AI-suggested and labelled so on the page.
++ */
++export interface FoodRecipeSuggestion {
++ id: string;
++ created_at: string;
++ ask: string | null;
++ people: number | null;
++ ai_run_id: string | null;
++ /**
++ * @maxItems 3
++ */
++ recipes:
++ | []
++ | [
++ {
++ title: string;
++ serves?: number | null;
++ minutes?: number | null;
++ /**
++ * @minItems 1
++ * @maxItems 25
++ */
++ ingredients: [
++ {
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ },
++ ...{
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ }[]
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 20
++ */
++ steps:
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ notes?: string | null;
++ to_buy: {
++ slug: string;
++ name: string;
++ price: number | null;
++ }[];
++ to_buy_cost: number;
++ to_buy_cost_partial?: boolean;
++ cost_note: string;
++ }
++ ]
++ | [
++ {
++ title: string;
++ serves?: number | null;
++ minutes?: number | null;
++ /**
++ * @minItems 1
++ * @maxItems 25
++ */
++ ingredients: [
++ {
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ },
++ ...{
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ }[]
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 20
++ */
++ steps:
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ notes?: string | null;
++ to_buy: {
++ slug: string;
++ name: string;
++ price: number | null;
++ }[];
++ to_buy_cost: number;
++ to_buy_cost_partial?: boolean;
++ cost_note: string;
++ },
++ {
++ title: string;
++ serves?: number | null;
++ minutes?: number | null;
++ /**
++ * @minItems 1
++ * @maxItems 25
++ */
++ ingredients: [
++ {
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ },
++ ...{
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ }[]
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 20
++ */
++ steps:
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ notes?: string | null;
++ to_buy: {
++ slug: string;
++ name: string;
++ price: number | null;
++ }[];
++ to_buy_cost: number;
++ to_buy_cost_partial?: boolean;
++ cost_note: string;
++ }
++ ]
++ | [
++ {
++ title: string;
++ serves?: number | null;
++ minutes?: number | null;
++ /**
++ * @minItems 1
++ * @maxItems 25
++ */
++ ingredients: [
++ {
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ },
++ ...{
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ }[]
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 20
++ */
++ steps:
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ notes?: string | null;
++ to_buy: {
++ slug: string;
++ name: string;
++ price: number | null;
++ }[];
++ to_buy_cost: number;
++ to_buy_cost_partial?: boolean;
++ cost_note: string;
++ },
++ {
++ title: string;
++ serves?: number | null;
++ minutes?: number | null;
++ /**
++ * @minItems 1
++ * @maxItems 25
++ */
++ ingredients: [
++ {
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ },
++ ...{
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ }[]
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 20
++ */
++ steps:
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ notes?: string | null;
++ to_buy: {
++ slug: string;
++ name: string;
++ price: number | null;
++ }[];
++ to_buy_cost: number;
++ to_buy_cost_partial?: boolean;
++ cost_note: string;
++ },
++ {
++ title: string;
++ serves?: number | null;
++ minutes?: number | null;
++ /**
++ * @minItems 1
++ * @maxItems 25
++ */
++ ingredients: [
++ {
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ },
++ ...{
++ name: string;
++ amount: string | null;
++ from_pantry: boolean;
++ citations: number[];
++ }[]
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 20
++ */
++ steps:
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ notes?: string | null;
++ to_buy: {
++ slug: string;
++ name: string;
++ price: number | null;
++ }[];
++ to_buy_cost: number;
++ to_buy_cost_partial?: boolean;
++ cost_note: string;
++ }
++ ];
++}
++
++/** [email protected] (owner: food) */
++/**
++ * food.recipe-result@1: what food.recipes.suggest answers: the new suggestion (POST /api/v1/recipes/suggest, 201) or the caller's recent ones (GET /api/v1/recipes).
++ */
++export type FoodRecipeResult =
++ | FoodRecipeSuggestion
++ | {
++ /**
++ * @maxItems 20
++ */
++ recipes:
++ | []
++ | [FoodRecipeSuggestion]
++ | [FoodRecipeSuggestion, FoodRecipeSuggestion]
++ | [FoodRecipeSuggestion, FoodRecipeSuggestion, FoodRecipeSuggestion]
++ | [FoodRecipeSuggestion, FoodRecipeSuggestion, FoodRecipeSuggestion, FoodRecipeSuggestion]
++ | [FoodRecipeSuggestion, FoodRecipeSuggestion, FoodRecipeSuggestion, FoodRecipeSuggestion, FoodRecipeSuggestion]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ]
++ | [
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion,
++ FoodRecipeSuggestion
++ ];
++ };
ddiff --git a/manifests/capabilities/food.recipes.suggest.json b/manifests/capabilities/food.recipes.suggest.json
nnew file mode 100644
iindex 00000000..8d6f02c7
---- /dev/null
++++ b/manifests/capabilities/food.recipes.suggest.json
@@@ -0,0 +1,23 @@
++{
++ "id": "food.recipes.suggest",
++ "version": "1.0",
++ "owner": "food",
++ "status": "active",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "Recipe ideas from the caller's saved pantry: POST /api/v1/recipes/suggest runs OpenVibe.AI's food.suggest_recipes workflow for the caller (up to three recipes; ingredients from the pantry or cited to Food's food list; what is still to buy priced from Food's own typical prices), 10 a person a day; GET /api/v1/recipes lists the last 20. A person acting for themself needs no capability; an app or agent token needs this one.",
++ "inputSchema": "food.recipe-suggest-request@1",
++ "outputSchema": "food.recipe-result@1",
++ "permissions": [
++ "food:recipes:suggest"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "POST /api/v1/recipes/suggest",
++ "GET /api/v1/recipes"
++ ]
++}
ddiff --git a/manifests/capabilities/work.application.manage.json b/manifests/capabilities/work.application.manage.json
nnew file mode 100644
iindex 00000000..7643665c
---- /dev/null
++++ b/manifests/capabilities/work.application.manage.json
@@@ -0,0 +1,26 @@
++{
++ "id": "work.application.manage",
++ "version": "1.0",
++ "owner": "work",
++ "status": "active",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "Manage the caller's tracked job applications on OpenVibe.Work: list (GET /api/v1/me/applications), add from a listing or by hand (POST), read with its status history, edit status, notes, dates and next step (PATCH), and delete (DELETE). A person acting for themself needs no capability; an app or agent token needs this one. Asking the person's OpenVibe.Actor agent (POST …/assist) is the person's own act and takes no capability.",
++ "inputSchema": "work.application-write-request@1",
++ "outputSchema": "work.application-result@1",
++ "permissions": [
++ "work:application:manage"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/me/applications",
++ "POST /api/v1/me/applications",
++ "GET /api/v1/me/applications/:id",
++ "PATCH /api/v1/me/applications/:id",
++ "DELETE /api/v1/me/applications/:id"
++ ]
++}
ddiff --git a/manifests/services/food.json b/manifests/services/food.json
iindex 50400255..eb0c7ed0 100644
---- a/manifests/services/food.json
++++ b/manifests/services/food.json
@@@ -19,7 +19,8 @@
"food.plan.read",
"food.plan.write",
"food.pantry.read",
-- "food.pantry.write"
++ "food.pantry.write",
++ "food.recipes.suggest"
],
"eventsProduced": [],
"eventsConsumed": [
ddiff --git a/manifests/services/work.json b/manifests/services/work.json
iindex 8e0d4311..850465bb 100644
---- a/manifests/services/work.json
++++ b/manifests/services/work.json
@@@ -15,7 +15,9 @@
"state": "live",
"publicSite": "service"
},
-- "capabilities": [],
++ "capabilities": [
++ "work.application.manage"
++ ],
"eventsProduced": [
"work.index_document.upserted",
"work.index_document.deleted"
@@@ -71,7 +73,7 @@
"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.",
++ "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, work_saved_searches, work_applications and work_application_events. The agent pillar: a person tracks applications (status, notes, next steps, history; work.application.manage for apps and agents) and can ask their OpenVibe.Actor agent to help prepare one (actor.task.create with X-OV-Subject, idempotency key per assist, 10 a day).",
"health": "/api/health",
"ready": "/api/ready",
"internalOrigin": "http://127.0.0.1:4960",
ddiff --git a/package.json b/package.json
iindex 319db204..f0550588 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.146.0",
++ "version": "0.147.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",