openvibers/openvibe.contracts Commits
v0.23.0: OpenVibe.Deals is alpha (deals.* capabilities)
Changes
nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "deals.flag.create",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Report a problem with an offer for the acting person (expired, price_wrong, duplicate, spam, other). One open report per person and offer; rate-limited. Flags never change an offer by themselves.",++ "permissions": [++ "deals:write"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-write",++ "events": [],++ "implementedBy": [++ "POST /api/v1/offers/:id/flags"++ ]++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "deals.offer.expire",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Mark an active offer expired (the person who submitted it, or a moderator). Unknown expiry is never assumed; offers with a stated end expire on their own.",++ "permissions": [++ "deals:write"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "deals-write",++ "events": [++ "deals.offer.expired",++ "deals.index_document.upserted"++ ],++ "implementedBy": [++ "POST /api/v1/offers/:id/expire"++ ]++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "deals.offer.merge",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Duplicate resolution (moderation): merge an offer into another, or unmerge it. Nothing is copied or deleted: votes, observations, sources and comments stay where they were written and the canonical offer reads across the group (a voter counts once). Audited with the tallies before and after; reversible.",++ "permissions": [++ "deals:moderate"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-moderation",++ "events": [++ "deals.offer.updated",++ "deals.index_document.upserted",++ "deals.index_document.deleted"++ ],++ "implementedBy": [++ "POST /api/v1/offers/:id/merge",++ "POST /api/v1/offers/:id/unmerge"++ ]++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "id": "deals.offer.moderate",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Moderation: disable or enable an offer, record a person's review of imported or AI-assisted text (reviews are recorded for a usr_ subject only), read and resolve flags including vote-ring flags. Audited.",++ "permissions": [++ "deals:moderate"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-moderation",++ "events": [++ "deals.offer.updated",++ "deals.index_document.upserted",++ "deals.index_document.deleted"++ ],++ "implementedBy": [++ "POST /api/v1/offers/:id/disable",++ "POST /api/v1/offers/:id/enable",++ "POST /api/v1/offers/:id/review",++ "GET /api/v1/flags",++ "POST /api/v1/flags/:id/resolve"++ ]++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "deals.offer.submit",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Submit a deal for the acting person (X-OV-Subject): a link, title and what the person sees now (price with its currency, shipping, condition, availability — each optional, never defaulted). The first observation is recorded with its time. A link already posted answers 409 offer.duplicate with the existing offer. With X-OV-Origin: ai the text is AI-assisted: disclosed and noindex until a person reviews it. Rate-limited per person.",++ "permissions": [++ "deals:write"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-write",++ "events": [++ "deals.offer.created",++ "deals.watch.matched",++ "deals.index_document.upserted"++ ],++ "implementedBy": [++ "POST /api/v1/offers"++ ]++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "deals.offer.update",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Edit an offer the acting person submitted (title, description, category, stated expiry, product link), or record a new observation of any active offer for the acting person (POST …/observations: price with currency, shipping, condition, availability, with the time it was observed). Prices are never edited in place; each observation is a new row. Services may state observed_at (never in the future).",++ "permissions": [++ "deals:write"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "deals-write",++ "events": [++ "deals.offer.updated",++ "deals.watch.matched",++ "deals.index_document.upserted"++ ],++ "implementedBy": [++ "PATCH /api/v1/offers/:id",++ "POST /api/v1/offers/:id/observations"++ ]++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "deals.product.resolve",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Resolve a product by its aliases (gtin, mpn, sku, exact name, url), creating it when none matches (create: false to only look up). Aliases that already name another product are reported as conflicts, never re-pointed.",++ "permissions": [++ "deals:write"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-write",++ "events": [++ "deals.index_document.upserted"++ ],++ "implementedBy": [++ "POST /api/v1/products/resolve"++ ]++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "deals.vote.remove",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Remove the acting person's vote on an offer (kept as a removed row for history). Same abuse controls as deals.vote.set.",++ "permissions": [++ "deals:vote"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-vote",++ "events": [++ "deals.vote.changed"++ ],++ "implementedBy": [++ "DELETE /api/v1/offers/:id/vote"++ ]++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "deals.vote.set",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Set the acting person's vote (+1 / -1) on an offer. Server-authoritative: weight and hotness are computed by Deals (hot@1), never taken from the request. Rate-limited per person, per IP hash and per offer; no votes on one's own offer; vote-ring detection flags moderators.",++ "permissions": [++ "deals:vote"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "deals-vote",++ "events": [++ "deals.vote.changed"++ ],++ "implementedBy": [++ "PUT /api/v1/offers/:id/vote"++ ]++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "deals.watch.create",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Create a keyword, product or price-below watch, or a saved search (never notifies), for the acting person. A watch notifies at most once per observation (deals.watch.matched, internal; Deals never emails).",++ "permissions": [++ "deals:watch"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "deals-write",++ "events": [],++ "implementedBy": [++ "POST /api/v1/watches"++ ]++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "deals.watch.delete",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "Delete one of the acting person's watches or saved searches.",++ "permissions": [++ "deals:watch"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "deals-write",++ "events": [],++ "implementedBy": [++ "DELETE /api/v1/watches/:id"++ ]++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "deals.watch.read",++ "version": "1.0",++ "owner": "deals",++ "status": "active",++ "visibility": "first-party",++ "description": "List the acting person's watches and saved searches with their notification counts.",++ "permissions": [++ "deals:watch"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "deals-read",++ "events": [],++ "implementedBy": [++ "GET /api/v1/watches"++ ]++}@@@ -1,16 +1,48 @@{"version": "0.1.0","contractRanges": {-- "openvibe-contracts": "^0.1.0"++ "openvibe-contracts": ">=0.19.0 <1.0.0"},"id": "deals","name": "OpenVibe.Deals",-- "status": "placeholder",++ "status": "alpha","repository": "OpenVibers/OpenVibe.Deals",-- "domains": [],-- "capabilities": [],-- "eventsProduced": [],-- "eventsConsumed": [],-- "namespacesOwned": [],-- "notes": "Deals submitted and voted on by the community, with source, price and freshness always shown. Charter only; no runtime."++ "domains": [++ "openvibe.deals"++ ],++ "publicOrigin": "https://openvibe.deals",++ "health": "/api/health",++ "ready": "/api/ready",++ "capabilities": [++ "deals.offer.submit",++ "deals.offer.update",++ "deals.offer.expire",++ "deals.offer.merge",++ "deals.offer.moderate",++ "deals.vote.set",++ "deals.vote.remove",++ "deals.watch.create",++ "deals.watch.read",++ "deals.watch.delete",++ "deals.product.resolve",++ "deals.flag.create"++ ],++ "eventsProduced": [++ "deals.offer.created",++ "deals.offer.updated",++ "deals.offer.expired",++ "deals.vote.changed",++ "deals.watch.matched",++ "deals.index_document.upserted",++ "deals.index_document.deleted"++ ],++ "eventsConsumed": [++ "sources.item.created",++ "sources.item.updated",++ "sources.item.removed"++ ],++ "namespacesOwned": [++ "deals.*"++ ],++ "notes": "Port 4840. Owns deal_products, deal_product_aliases, deal_offers, deal_offer_sources, deal_votes, deal_hotness_snapshots, deal_watches, deal_price_observations, deal_flags (+ deal_stores) in its own SQLite. Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Sources (sources.item.read; sources.item.* events only wake the pull), OpenVibe.Community (community.comment.write, community.comment.moderate) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads deals.index_document.* from Events; deals.watch.matched is for Network notifications (consumer not built yet)."}@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.22.0",++ "version": "0.23.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",
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "deals.flag.create",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Report a problem with an offer for the acting person (expired, price_wrong, duplicate, spam, other). One open report per person and offer; rate-limited. Flags never change an offer by themselves.",
+ "permissions": [
+ "deals:write"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-write",
+ "events": [],
+ "implementedBy": [
+ "POST /api/v1/offers/:id/flags"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "deals.offer.expire",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Mark an active offer expired (the person who submitted it, or a moderator). Unknown expiry is never assumed; offers with a stated end expire on their own.",
+ "permissions": [
+ "deals:write"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "deals-write",
+ "events": [
+ "deals.offer.expired",
+ "deals.index_document.upserted"
+ ],
+ "implementedBy": [
+ "POST /api/v1/offers/:id/expire"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "deals.offer.merge",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Duplicate resolution (moderation): merge an offer into another, or unmerge it. Nothing is copied or deleted: votes, observations, sources and comments stay where they were written and the canonical offer reads across the group (a voter counts once). Audited with the tallies before and after; reversible.",
+ "permissions": [
+ "deals:moderate"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-moderation",
+ "events": [
+ "deals.offer.updated",
+ "deals.index_document.upserted",
+ "deals.index_document.deleted"
+ ],
+ "implementedBy": [
+ "POST /api/v1/offers/:id/merge",
+ "POST /api/v1/offers/:id/unmerge"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "id": "deals.offer.moderate",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Moderation: disable or enable an offer, record a person's review of imported or AI-assisted text (reviews are recorded for a usr_ subject only), read and resolve flags including vote-ring flags. Audited.",
+ "permissions": [
+ "deals:moderate"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-moderation",
+ "events": [
+ "deals.offer.updated",
+ "deals.index_document.upserted",
+ "deals.index_document.deleted"
+ ],
+ "implementedBy": [
+ "POST /api/v1/offers/:id/disable",
+ "POST /api/v1/offers/:id/enable",
+ "POST /api/v1/offers/:id/review",
+ "GET /api/v1/flags",
+ "POST /api/v1/flags/:id/resolve"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "deals.offer.submit",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Submit a deal for the acting person (X-OV-Subject): a link, title and what the person sees now (price with its currency, shipping, condition, availability — each optional, never defaulted). The first observation is recorded with its time. A link already posted answers 409 offer.duplicate with the existing offer. With X-OV-Origin: ai the text is AI-assisted: disclosed and noindex until a person reviews it. Rate-limited per person.",
+ "permissions": [
+ "deals:write"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-write",
+ "events": [
+ "deals.offer.created",
+ "deals.watch.matched",
+ "deals.index_document.upserted"
+ ],
+ "implementedBy": [
+ "POST /api/v1/offers"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "deals.offer.update",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Edit an offer the acting person submitted (title, description, category, stated expiry, product link), or record a new observation of any active offer for the acting person (POST …/observations: price with currency, shipping, condition, availability, with the time it was observed). Prices are never edited in place; each observation is a new row. Services may state observed_at (never in the future).",
+ "permissions": [
+ "deals:write"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "deals-write",
+ "events": [
+ "deals.offer.updated",
+ "deals.watch.matched",
+ "deals.index_document.upserted"
+ ],
+ "implementedBy": [
+ "PATCH /api/v1/offers/:id",
+ "POST /api/v1/offers/:id/observations"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "deals.product.resolve",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Resolve a product by its aliases (gtin, mpn, sku, exact name, url), creating it when none matches (create: false to only look up). Aliases that already name another product are reported as conflicts, never re-pointed.",
+ "permissions": [
+ "deals:write"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-write",
+ "events": [
+ "deals.index_document.upserted"
+ ],
+ "implementedBy": [
+ "POST /api/v1/products/resolve"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "deals.vote.remove",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Remove the acting person's vote on an offer (kept as a removed row for history). Same abuse controls as deals.vote.set.",
+ "permissions": [
+ "deals:vote"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-vote",
+ "events": [
+ "deals.vote.changed"
+ ],
+ "implementedBy": [
+ "DELETE /api/v1/offers/:id/vote"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "deals.vote.set",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Set the acting person's vote (+1 / -1) on an offer. Server-authoritative: weight and hotness are computed by Deals (hot@1), never taken from the request. Rate-limited per person, per IP hash and per offer; no votes on one's own offer; vote-ring detection flags moderators.",
+ "permissions": [
+ "deals:vote"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "deals-vote",
+ "events": [
+ "deals.vote.changed"
+ ],
+ "implementedBy": [
+ "PUT /api/v1/offers/:id/vote"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "deals.watch.create",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Create a keyword, product or price-below watch, or a saved search (never notifies), for the acting person. A watch notifies at most once per observation (deals.watch.matched, internal; Deals never emails).",
+ "permissions": [
+ "deals:watch"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "deals-write",
+ "events": [],
+ "implementedBy": [
+ "POST /api/v1/watches"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "deals.watch.delete",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Delete one of the acting person's watches or saved searches.",
+ "permissions": [
+ "deals:watch"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "deals-write",
+ "events": [],
+ "implementedBy": [
+ "DELETE /api/v1/watches/:id"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "deals.watch.read",
+ "version": "1.0",
+ "owner": "deals",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "List the acting person's watches and saved searches with their notification counts.",
+ "permissions": [
+ "deals:watch"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "deals-read",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/watches"
+ ]
+}
@@ -1,16 +1,48 @@
@@ -1,16 +1,48 @@
{
{
"version": "0.1.0",
"version": "0.1.0",
"contractRanges": {
"contractRanges": {
- "openvibe-contracts": "^0.1.0"
+ "openvibe-contracts": ">=0.19.0 <1.0.0"
},
},
"id": "deals",
"id": "deals",
"name": "OpenVibe.Deals",
"name": "OpenVibe.Deals",
- "status": "placeholder",
+ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Deals",
"repository": "OpenVibers/OpenVibe.Deals",
- "domains": [],
- "capabilities": [],
- "eventsProduced": [],
- "eventsConsumed": [],
- "namespacesOwned": [],
- "notes": "Deals submitted and voted on by the community, with source, price and freshness always shown. Charter only; no runtime."
+ "domains": [
+ "openvibe.deals"
+ ],
+ "publicOrigin": "https://openvibe.deals",
+ "health": "/api/health",
+ "ready": "/api/ready",
+ "capabilities": [
+ "deals.offer.submit",
+ "deals.offer.update",
+ "deals.offer.expire",
+ "deals.offer.merge",
+ "deals.offer.moderate",
+ "deals.vote.set",
+ "deals.vote.remove",
+ "deals.watch.create",
+ "deals.watch.read",
+ "deals.watch.delete",
+ "deals.product.resolve",
+ "deals.flag.create"
+ ],
+ "eventsProduced": [
+ "deals.offer.created",
+ "deals.offer.updated",
+ "deals.offer.expired",
+ "deals.vote.changed",
+ "deals.watch.matched",
+ "deals.index_document.upserted",
+ "deals.index_document.deleted"
+ ],
+ "eventsConsumed": [
+ "sources.item.created",
+ "sources.item.updated",
+ "sources.item.removed"
+ ],
+ "namespacesOwned": [
+ "deals.*"
+ ],
+ "notes": "Port 4840. Owns deal_products, deal_product_aliases, deal_offers, deal_offer_sources, deal_votes, deal_hotness_snapshots, deal_watches, deal_price_observations, deal_flags (+ deal_stores) in its own SQLite. Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Sources (sources.item.read; sources.item.* events only wake the pull), OpenVibe.Community (community.comment.write, community.comment.moderate) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads deals.index_document.* from Events; deals.watch.matched is for Network notifications (consumer not built yet)."
}
}
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.22.0",
+ "version": "0.23.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",