NewsSourceIngestedPayload news.source.ingested@1
Generated at from
openvibe-contracts v0.34.2 and
openvibe-sdk v0.5.0.
- Version
- 1.0.0
- Owner
- news
- Visibility
- first-party
- Status
- active
- Compatibility
- backward
- Schema
https://openvibe.network/contracts/events/payloads/news.source.ingested.v1.json
news.source.ingested v1 (OpenVibe.News server/domain/ingest.js ingestedEvent, from apply and applyRemoval). An OpenVibe.Sources item of category news changed what News holds, in the transaction that applied it: created (a new news_source_items row, with its dedupe outcome and the cluster it joined; news.cluster.updated follows in the same transaction), updated (Sources sent a newer revision of an item News has; adds previous_revision) or removed (Sources removed the item: sticky, the stored summary is dropped; adds reason and origin). Items arrive through the Events webhook (sources.item.*) or the cursor pull; a replay, an older or equal revision, an item of another category, an update to a removed item and a removal of an item never ingested emit nothing, and an item without a title is news.source.failed (state rejected) instead. Deliberately left out: the headline, outlet, authors, published_at and licensed summary (GET /api/v1/source-items/:id with news.cluster.read) and the cluster_reason explanation. Envelope: subject { type: source_item, id: nsi_… }, visibility internal, priority low, actor service:news.
Fields
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
item_id | string | yes | News' own source item (news_source_items). |
|
action | enum | yes |
| |
status | enum | yes | The item after the change: active or duplicate for created and updated, removed exactly when action is removed. |
|
sources_item | object | yes | Typed reference (common/entity-ref@1 shape) to the OpenVibe.Sources item and the Sources revision News now holds. |
|
sources_item.service | const | yes |
| |
sources_item.type | const | yes |
| |
sources_item.id | string | yes | The Sources item id (itm_…), as Sources gave it. |
|
sources_item.revision | integer | yes | Sources revision the stored fields came from (1 when Sources sent none). | |
source_key | string | yes | The Sources source the item came from, as Sources gave it ('unknown' when it gave none). |
|
canonical_url | string | null | yes | The item's canonical URL as Sources gave it (not validated by News); null when it had none. | |
duplicate_of | string | null | yes | The original item when status is duplicate (the root of any duplicate chain); null otherwise. |
|
dedupe_rule | enum | yes | Which dedupe rule matched when the item was first ingested: same normalised URL, same Sources content hash, or a near-identical headline within 48 h; null when it is not a duplicate. |
|
cluster_id | string | null | yes | The cluster the item belongs to (every ingested item is placed in one). |
|
previous_revision | integer | action updated only: the Sources revision News held before. | ||
reason | string | action removed only: why Sources removed it ('removed upstream' when it gave no reason). |
| |
origin | enum | action removed only: whether the removal came as a sources.item.removed delivery or through the cursor pull. |
|
Examples
From the contract's own test fixtures: valid ones validate, rejected ones must fail.
Valid: created-duplicate
{
"item_id": "nsi_01K5RZ4F0A6H3K8PQ2S5W7Y9ZE",
"action": "created",
"status": "duplicate",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ3X8Y0Z2B4D6F8H0K2M4P",
"revision": 1
},
"source_key": "paper-b",
"canonical_url": "https://www.wire-a.example/space/europa-clipper-launch/?utm_source=feed",
"duplicate_of": "nsi_01K5RYX2C4E6G8J0M2P4R6T8VA",
"dedupe_rule": "canonical_url",
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T"
}Valid: created
{
"item_id": "nsi_01K5RZ3M8Q2W7T4XC9V6B1N0JD",
"action": "created",
"status": "active",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ2V7W9Y1A3C5E7G9J1K3M",
"revision": 1
},
"source_key": "wire-a",
"canonical_url": "https://wire-a.example/space/europa-clipper-launch",
"duplicate_of": null,
"dedupe_rule": null,
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T"
}Valid: removed
{
"item_id": "nsi_01K5RZ3M8Q2W7T4XC9V6B1N0JD",
"action": "removed",
"status": "removed",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ2V7W9Y1A3C5E7G9J1K3M",
"revision": 3
},
"source_key": "wire-a",
"canonical_url": null,
"duplicate_of": null,
"dedupe_rule": null,
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T",
"reason": "licence withdrawn by the publisher",
"origin": "webhook"
}Valid: updated
{
"item_id": "nsi_01K5RZ3M8Q2W7T4XC9V6B1N0JD",
"action": "updated",
"status": "active",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ2V7W9Y1A3C5E7G9J1K3M",
"revision": 2
},
"source_key": "wire-a",
"canonical_url": "https://wire-a.example/space/europa-clipper-launch",
"duplicate_of": null,
"dedupe_rule": null,
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T",
"previous_revision": 1
}Rejected: bad-item-id
{
"item_id": "itm_01K5RZ2V7W9Y1A3C5E7G9J1K3M",
"action": "created",
"status": "active",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ2V7W9Y1A3C5E7G9J1K3M",
"revision": 1
},
"source_key": "wire-a",
"canonical_url": null,
"duplicate_of": null,
"dedupe_rule": null,
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T"
}Rejected: headline-leaked
{
"item_id": "nsi_01K5RZ3M8Q2W7T4XC9V6B1N0JD",
"action": "created",
"status": "active",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ2V7W9Y1A3C5E7G9J1K3M",
"revision": 1
},
"source_key": "wire-a",
"canonical_url": null,
"duplicate_of": null,
"dedupe_rule": null,
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T",
"headline": "NASA launches Europa Clipper"
}Rejected: missing-sources-item
{
"item_id": "nsi_01K5RZ3M8Q2W7T4XC9V6B1N0JD",
"action": "created",
"status": "active",
"source_key": "wire-a",
"canonical_url": null,
"duplicate_of": null,
"dedupe_rule": null,
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T"
}Rejected: unknown-dedupe-rule
{
"item_id": "nsi_01K5RZ4F0A6H3K8PQ2S5W7Y9ZE",
"action": "created",
"status": "duplicate",
"sources_item": {
"service": "sources",
"type": "item",
"id": "itm_01K5RZ3X8Y0Z2B4D6F8H0K2M4P",
"revision": 1
},
"source_key": "paper-b",
"canonical_url": null,
"duplicate_of": "nsi_01K5RYX2C4E6G8J0M2P4R6T8VA",
"dedupe_rule": "fuzzy",
"cluster_id": "clu_01K5RZ3M8R5D1F7H3K9N2Q4S6T"
}Validate
const contracts = require('openvibe-contracts');
contracts.validate('news.source.ingested@1', value); // { valid, errors: [{ path, message }] }