# Data Product Specification: [Data product name]

> Specification of one data product: why it exists and who consumes it, the owning domain and team, the input ports it reads from, the output ports it promises (each backed by a data contract), the management ports for discovery, observability and control, the service level objectives, discoverability metadata, access terms and internal pricing, lifecycle and deprecation, and the context that AI agents and assistants need to use it safely. The field names follow the Bitol Open Data Product Standard (ODPS) v1.1.0, and each output port links to a contract written with the Bitol Open Data Contract Standard (ODCS) v3.2.0, so the prose document and the YAML file in section 14 describe the same thing. Replace every `[bracketed]` value and every row marked *Example*.

<!--
Guidance: How to use this template
- One document per data product. A data product is a unit that a domain team owns end to end: code, data, metadata, contracts and infrastructure configuration. A single table that nobody owns is not a data product, however good its schema.
- Write the product view here and the dataset view in the contracts. Schema, quality rules, service level properties and price belong in each output port's Data Contract (`data/data-contract`); this document points to them and explains how the ports fit together.
- Keep this document and the ODPS YAML file (section 14) in the same repository and change them in one reviewed pull request. The YAML feeds the catalogue; the prose explains the decisions behind it.
- Two unrelated specifications share the acronym "ODPS". This template uses the Bitol Open Data Product Standard (LF AI and Data, v1.1.0). The Open Data Product Specification published at opendataproducts.org (version 4.1) is a different model with its own fields. Say in Document control which one your catalogue uses and never mix fields from both in one file.
- The worked examples use Corvid Commerce, a fictional SaaS retailer whose lakehouse runs on Apache Iceberg tables transformed with dbt. The product described is its "Orders" data product (tenant `corvid-commerce`, Sales domain). Its main output port is `orders` version 2.1.0, governed by the contract `corvid-orders-orders` that the Data Contract template describes. Contract IDs follow `corvid-<product>-<port>`.
- Related documents: Data Pipeline Specification (`data/data-pipeline-spec`), Data Quality Standard (`data/data-quality-standard`), Data Classification Policy (`data/data-classification-policy`), Data Retention and Deletion Standard (`data/data-retention-deletion-standard`), Semantic Model Specification (`data/semantic-model-specification`), Metric Definition (`data/metric-definition`), Data Governance Operating Model (`data/data-governance-operating-model`), Data Platform Architecture (`architecture/data-platform-architecture`), Team API (`architecture/team-api`).
-->

## Document control

<!-- Guidance: Identify the product, its domain and owner, the product version this document describes and the specification the YAML follows. A new major product version gets a new revision of this document, not a new document. -->

| Field | Value |
|---|---|
| Document ID | [DPS-ORD-001] |
| Data product name | [Orders] |
| Data product ID (ODPS `id`) | [Stable identifier, for example a UUID] |
| Product version (ODPS `version`) | [2.1.0] |
| Product status (ODPS `status`) | [proposed / draft / active / deprecated / retired] |
| Domain (ODPS `domain`) | [Sales] |
| Architectural type (ODPS `type`) | [sourceAligned / aggregate / consumerAligned] |
| Data product owner | [@product-owner] |
| Owning team | [Team name, link to its Team API] |
| Specification used for the YAML | Bitol Open Data Product Standard v1.1.0 (`apiVersion: v1.1.0`) |
| Contract specification | Bitol Open Data Contract Standard v3.2.0 |
| Document version | [0.1] |
| Status of this document | [Draft / In review / Approved / Superseded] |
| Owner | [@product-owner] |
| Approvers/reviewers | [@domain-data-owner], [@data-steward], [@platform-lead], [@privacy-officer] |
| Classification | [Internal] |
| Last updated | YYYY-MM-DD |
| Repository path | [git URL and path of this file and the YAML] |

### Approvals

| Role | Name | Decision | Date |
|---|---|---|---|
| Domain data owner | [@name] | [Approve / Approve with conditions / Reject] | YYYY-MM-DD |
| Data steward | [@name] | [Decision] | YYYY-MM-DD |
| Data platform lead | [@name] | [Decision] | YYYY-MM-DD |
| Privacy officer (when personal data is in scope) | [@name] | [Decision] | YYYY-MM-DD |

### Revision history

| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft |
| 1.0 | YYYY-MM-DD | [@name] | *Example:* Approved for product version 2.0.0, catalogue entry published |
| 1.1 | YYYY-MM-DD | [@name] | *Example:* Added output port `orders_daily_agg` and its contract |

## Contents

- [1. Product overview](#1-product-overview)
- [2. Ownership and domain](#2-ownership-and-domain)
- [3. Input ports](#3-input-ports)
- [4. Output ports](#4-output-ports)
- [5. Management ports](#5-management-ports)
- [6. Service level objectives](#6-service-level-objectives)
- [7. Discoverability metadata](#7-discoverability-metadata)
- [8. Access, usage terms and pricing](#8-access-usage-terms-and-pricing)
- [9. AI usage context](#9-ai-usage-context)
- [10. Support and communication](#10-support-and-communication)
- [11. Lifecycle, versioning and deprecation](#11-lifecycle-versioning-and-deprecation)
- [12. Security, privacy and compliance](#12-security-privacy-and-compliance)
- [13. Traceability matrix](#13-traceability-matrix)
- [14. ODPS YAML skeleton](#14-odps-yaml-skeleton)
- [15. Glossary and references](#15-glossary-and-references)
- [Standard and licence note](#standard-and-licence-note)

## 1. Product overview

<!-- Guidance: State the business problem the product solves and for whom, in words a consumer would use. Questions: what decision or process depends on it, what does it replace, what is out of scope? Pitfall: describing the pipeline instead of the value. -->

### 1.1 Purpose

[Two or three sentences on why the product exists. This text maps to `description.purpose` in the YAML.]

*Example:* The Orders data product publishes one trusted, conformed record of every confirmed customer order across the web shop, the mobile app, physical stores and the marketplace channel. It is the single source for revenue reporting, demand forecasting and customer analytics, and it replaces four channel-specific extracts that disagreed on order counts by up to two per cent.

### 1.2 Consumers and use cases

List the known consumer groups and what each one needs. A consumer group that is not listed will not be told about breaking changes.

| ID | Consumer group | Use case | Output port used | Criticality |
|---|---|---|---|---|
| UC-001 *(Example)* | Finance analytics | Daily net revenue by channel and currency | `orders` | High |
| UC-002 *(Example)* | Demand planning (data science) | Training features for the weekly demand forecast | `orders_daily_agg` | High |
| UC-003 *(Example)* | Customer analytics | Cohort and repeat-purchase analysis | `orders` | Medium |
| UC-004 *(Example)* | Internal analytics assistant | Answer governed questions on order volumes | `orders_daily_agg` | Medium |
| UC-005 *(Example)* | Checkout data product (Sales domain) | Join orders to checkout sessions for the conversion funnel | `orders` | Medium |
| [UC-nnn] | [Group] | [Use case] | [Port] | [High / Medium / Low] |

### 1.3 Scope, usage and limitations

| Item | Description (maps to `description.usage` and `description.limitations`) |
|---|---|
| Recommended usage | *Example:* Revenue and volume analytics at order grain or daily grain; model training with the aggregate port |
| Not suitable for | *Example:* Real-time fraud decisions (data can be up to 60 minutes old); statutory financial reporting (use the general ledger) |
| Known limitations | *Example:* Orders before 2022-01-01 have no channel; marketplace orders can arrive up to 24 hours late |
| Out of scope | *Example:* Returns and refunds (owned by the Returns product); payment card data (never collected by the platform) |

### 1.4 Product type and position

State whether the product is source aligned (close to an operational system), aggregate (combines several sources) or consumer aligned (shaped for one family of use cases). The ODPS `type` field accepts these three common values, and organisations may define their own. Explain where the product sits in the domain landscape with a small diagram.

```mermaid
flowchart LR
  OS[Order service events] --> IP1[Input port: order_events]
  CAT[Catalogue product: Products] --> IP2[Input port: products_v3]
  FXR[Finance reference data: FX rates] --> IP3[Input port: fx_rates]
  IP1 --> P((Orders data product))
  IP2 --> P
  IP3 --> P
  P --> OP1[Output port: orders 2.1.0]
  P --> OP2[Output port: orders_daily_agg]
  P --- MP[Management ports: discoverability, observability, control]
  OP1 --> C1[Finance analytics]
  OP1 --> C4[Checkout data product]
  OP2 --> C2[Demand planning]
  OP2 --> C3[Analytics assistant]
```

## 2. Ownership and domain

<!-- Guidance: Name the people accountable for the product and record when they joined, so ownership survives reorganisations. Pitfall: listing a platform team as owner; the platform runs the infrastructure, the domain owns the product. -->

### 2.1 Domain

| Field | Value |
|---|---|
| Business domain | *Example:* Sales |
| Sub-domain or bounded context | *Example:* Order management |
| Tenant (ODPS `tenant`) | *Example:* `corvid-commerce` |
| Domain data owner (accountable for the data) | [@name] |
| Data product owner (accountable for the product) | [@name] |
| Data steward (definitions and quality) | [@name] |

Domains and ownership boundaries come from the Data Governance Operating Model (`data/data-governance-operating-model`). If this product does not fit the domain map, raise it with the governance council before publishing rather than inventing a new domain here.

### 2.2 Team

The team block maps to `team` in the YAML. Record members by stable username, with a role, so the catalogue can route access requests and incidents.

| Username | Name | Role (ODPS `role`) | Date in | Date out | Replaced by |
|---|---|---|---|---|---|
| *Example:* `a.okafor` | Ada Okafor | owner | 2025-03-01 | | |
| *Example:* `m.lindqvist` | Mats Lindqvist | data steward | 2025-03-01 | | |
| *Example:* `j.kim` | [Name] | data engineer | 2025-06-15 | | |
| *Example:* `r.silva` | [Name] | data engineer | 2024-03-01 | 2026-05-31 | `j.kim` |
| [username] | [Name] | [role] | YYYY-MM-DD | [YYYY-MM-DD] | [username] |

Link the owning team's Team API (`architecture/team-api`) for working hours, interaction modes and how to request work.

## 3. Input ports

<!-- Guidance: List every source the product reads, with the contract that governs it. Questions: who owns each source, which version is consumed, what happens when the source breaks its contract? Pitfall: reading an operational database directly without any contract, which hides the dependency from its owner. -->

Each input port declares a dependency on another data product's output port or on an operational source. In ODPS an input port carries a `name`, a `version` and a `contractId`. Where a source has no contract yet, record that as a risk in section 13 and agree one with the source owner.

| ID | Input port name | Source and owner | Version consumed | Contract ID | Read pattern | On contract breach |
|---|---|---|---|---|---|---|
| IN-001 *(Example)* | `order_events` | Order service event stream (operational source), Order management team | 3.0.0 | `corvid-ordersvc-order-events` | Streaming, micro-batch every 5 minutes | Pause ingestion, quarantine, alert the order service on-call |
| IN-002 *(Example)* | `products_v3` | Products data product, Catalogue domain | 3.2.0 | `corvid-products-products` | Daily snapshot | Use previous snapshot, flag product attributes as stale |
| IN-003 *(Example)* | `fx_rates` | Treasury reference data, Finance domain | 1.1.0 | `corvid-fx-fx-rates` | Daily | Hold currency conversion, publish in original currency only |
| [IN-nnn] | [name] | [Source, owner] | [x.y.z] | [contract ID] | [Pattern] | [Action] |

Describe the transformation logic only at the level a consumer needs. The detailed jobs, schedules and recovery steps live in the Data Pipeline Specification (`data/data-pipeline-spec`), and dbt conventions in the dbt Project Standard (`data/dbt-project-standard`).

## 4. Output ports

<!-- Guidance: Each output port is a promise. Give it a name, a version, a type and a contract. Questions: which consumers use which port, how are versions run side by side, which inputs feed each port? Pitfall: exposing internal staging tables as ports because someone asked once. -->

### 4.1 Port register

In ODPS the combination of output port `name` and `version` is the key, so two major versions of the same port appear as two entries while they run in parallel. Each port must reference exactly one contract (`contractId`) that defines schema, quality rules, service level properties and price. In the example, the active and the deprecated order grain ports share the name `orders` and the contract ID `corvid-orders-orders`; the version (2.1.0 or 1.4.0) tells them apart.

| ID | Port name | Version | Type | Description | Contract ID | Input contracts (id, version) | Status |
|---|---|---|---|---|---|---|---|
| OUT-001 *(Example)* | `orders` | 2.1.0 | tables (Iceberg) | One row per confirmed order, conformed across channels | `corvid-orders-orders` | `corvid-ordersvc-order-events` 3.0.0; `corvid-fx-fx-rates` 1.1.0 | Active |
| OUT-002 *(Example)* | `orders_daily_agg` | 1.0.0 | tables (Iceberg) | Daily order count and net revenue by channel and currency | `corvid-orders-orders-daily-agg` | `corvid-orders-orders` 2.1.0 | Active |
| OUT-003 *(Example)* | `orders` | 1.4.0 | tables (Iceberg) | Previous order grain port (`net_amount` gross of discounts), kept for migration | `corvid-orders-orders` (version 1.4.0) | `corvid-ordersvc-order-events` 2.4.0 | Deprecated, sunset 2026-12-31 |
| [OUT-nnn] | [name] | [x.y.z] | [type] | [description] | [contract ID] | [list] | [status] |

### 4.2 Port details

Repeat this block for each output port.

| Field | OUT-001 *(Example)* |
|---|---|
| Physical location | `lakehouse.sales.orders` (Iceberg table, partitioned by order date); the deprecated 1.4.0 port reads `lakehouse.sales.orders_v1` |
| Access interfaces | SQL through the warehouse engine; Iceberg REST catalogue for engines with read access |
| Grain | One row per order |
| Refresh | Micro-batch every 15 minutes |
| Contract file | `contracts/corvid-orders-orders.yaml` |
| Software bill of materials (ODPS `sbom`) | Link to the CycloneDX or SPDX file for the transformation code, if your platform produces one |
| Port-level AI context | See section 9 |
| Synonyms | "sales orders", "customer orders" |

### 4.3 Contract alignment rules

- The port version and the contract version move together: a breaking change in the contract is a new major port version.
- Quality rules use the vocabulary and severity levels of the Data Quality Standard (`data/data-quality-standard`); do not invent new dimension names in a contract.
- Column classifications in the contract come from the Data Classification Policy (`data/data-classification-policy`).
- Metrics derived from the port, such as net revenue, are defined once in a Metric Definition (`data/metric-definition`) or the Semantic Model Specification (`data/semantic-model-specification`), never restated with a different formula in the contract description.

## 5. Management ports

<!-- Guidance: Management ports let people and tools discover the product, observe its health and control it. Questions: where does the catalogue read metadata, where do quality and freshness signals go, who can trigger a backfill or pause? Pitfall: health signals that only the producing team can see. -->

ODPS defines management ports with a required `name` and `content`, where content is a free string, typically `discoverability`, `observability`, `control` or `dictionary`, and a `type` of `rest` (the default) or `topic`.

| ID | Name | Content | Type | URL or channel | Purpose |
|---|---|---|---|---|---|
| MGT-001 *(Example)* | `catalogue-metadata` | discoverability | rest | [https://catalogue.example.internal/api/products/orders] | Publishes the YAML, lineage and glossary links to the catalogue |
| MGT-002 *(Example)* | `orders-health` | observability | rest | [https://observability.example.internal/orders] | Freshness, volume, schema change and quality check results per port |
| MGT-003 *(Example)* | `orders-lineage-events` | observability | topic | [lineage.orders] | OpenLineage run events emitted by each job |
| MGT-004 *(Example)* | `orders-control` | control | rest | [https://platform.example.internal/products/orders/control] | Pause publication, trigger a backfill, rotate to the previous snapshot (restricted to the owning team) |
| [MGT-nnn] | [name] | [content] | [rest / topic] | [URL or channel] | [Purpose] |

State who may call each control operation and how calls are logged. Control actions that change what consumers see, such as a rollback to a previous snapshot, must also trigger a consumer notification through the announcements channel in section 10.

## 6. Service level objectives

<!-- Guidance: Summarise the objectives consumers can rely on and point to the contract property that formalises each one. Questions: how is each objective measured, over what window, and what happens when it is missed? Pitfall: promising objectives the platform cannot measure. -->

ODPS v1.1.0 has no separate service level block at product level; the formal service level properties live in each output port's ODCS contract (`slaProperties`). Use this section to summarise them for readers and to set the internal objectives the team manages against. ODCS recommends property names such as `availability`, `latency` (preferred to "freshness"), `frequency`, `retention`, `errorRate`, `timeToDetect`, `timeToNotify` and `timeToRepair`; use those names so tools can read them, and express completeness as a quality rule.

| ID | Port | Objective | Target | Measurement | Window | Contract property |
|---|---|---|---|---|---|---|
| SLO-001 *(Example)* | `orders` | Freshness | Latest confirmed order visible within 60 minutes | Now minus maximum loaded `order_ts`, checked every 15 minutes | Rolling 30 days, 99% of checks | `latency` (SLA-ORD-001) in `corvid-orders-orders` |
| SLO-002 *(Example)* | `orders` | Availability | Port queryable | Synthetic query each hour | Calendar month, 99.5% of hours | `availability` (SLA-ORD-005) in `corvid-orders-orders` |
| SLO-003 *(Example)* | `orders` | Completeness | Marketplace orders complete | Daily reconciliation with order service | 24 hours after order date | Completeness quality rules DQ-ORD-004 and DQ-ORD-005 in `corvid-orders-orders` |
| SLO-004 *(Example)* | `orders_daily_agg` | Timeliness | Previous day complete | Partition published | By 06:00 UTC daily | `timeOfAvailability` and `frequency` in `corvid-orders-orders-daily-agg` |
| SLO-005 *(Example)* | All | Support response | Acknowledge a consumer-reported issue | Ticket timestamps | Within 1 business hour | Custom property `supportResponse` (SLA-ORD-006 in `corvid-orders-orders`) |
| SLO-006 *(Example)* | All | Incident notification | Notify registered consumers of a detected incident | Alert time to announcement time | Within 1 hour of detection | `timeToNotify` in each contract |
| [SLO-nnn] | [Port] | [Objective] | [Target] | [Measure] | [Window] | [Property] |

When an objective is missed, follow the data incident process and record the event in a Data Incident Postmortem (`data/data-incident-postmortem`). Use the organisation's severity scale (SEV1 to SEV4) when classifying the incident. Repeated misses are reviewed with consumers before the objective is lowered; lowering an objective is a breaking change (section 11).

## 7. Discoverability metadata

<!-- Guidance: Give the catalogue enough to let someone find the product, understand it and judge whether to trust it, without contacting the team. Pitfall: tags that only the producing team understands. -->

| Field | Value |
|---|---|
| Name (ODPS `name`) | *Example:* Orders |
| Synonyms (ODPS `synonyms`) | *Example:* "sales orders" (source: glossary), "commandes" (locale `fr-FR`, source: finance-team) |
| Tags (ODPS `tags`) | *Example:* `sales`, `orders`, `revenue`, `tier-1`, `PD`, `PSEUDO` (flags from the Data Classification Policy) |
| Authoritative definitions (ODPS `authoritativeDefinitions`) | *Example:* Business glossary entry "Order"; metric definition "Net revenue"; this document |
| Lineage | *Example:* Published from OpenLineage events (MGT-003) and shown in the catalogue |
| Quality score shown in catalogue | *Example:* Pass rate of contract checks over 30 days |
| Certification | *Example:* Certified by the data steward on YYYY-MM-DD; review every 12 months |
| Sample queries | Link to three to five reviewed example queries |
| Created (ODPS `productCreatedTs`) | YYYY-MM-DDTHH:MM:SSZ |

Synonyms in ODPS v1.1.0 can carry a locale (BCP 47 tag), a source and a status. Use them for legacy names consumers still search for and for other-language business terms, so both people and AI tools resolve the same product.

Custom properties (`customProperties`) hold organisation-specific facts that the standard does not model, such as cost centre or certification level. Keep their names in camel case and list each custom property your organisation allows in the Data Governance Operating Model standards register, so that catalogues do not fill up with one-off keys.

## 8. Access, usage terms and pricing

<!-- Guidance: Explain how a consumer gets access, under which terms, and what it costs if your organisation charges back. Questions: who approves, how long does approval take, which roles exist, what purposes are allowed? Pitfall: granting broad access by default and restricting later. -->

### 8.1 Access process

| Step | Description |
|---|---|
| 1. Request | *Example:* Consumer raises a request in the access portal naming the port, the purpose and the expected retention |
| 2. Purpose check | *Example:* Data steward checks the purpose against the allowed purposes below |
| 3. Privacy check | *Example:* Privacy officer reviews requests that include personal or pseudonymous columns |
| 4. Grant | *Example:* Role assigned through the access management system, time limited to 12 months |
| 5. Register | *Example:* Consumer added to the consumer register and to the announcements channel |
| 6. Review | *Example:* Access recertified every 12 months; unused grants removed after 90 days |

### 8.2 Roles

| Role | Ports | Columns | Approver |
|---|---|---|---|
| *Example:* `orders_reader` | `orders`, `orders_daily_agg` | All except `net_amount` at order grain | Data steward |
| *Example:* `orders_finance` | `orders` | All columns | Data steward, then finance controller |
| *Example:* `orders_agg_reader` | `orders_daily_agg` | All | Self-service |
| [role] | [ports] | [columns] | [approver] |

### 8.3 Allowed and prohibited purposes

| Purpose | Allowed | Condition |
|---|---|---|
| *Example:* Revenue and volume reporting | Yes | None |
| *Example:* Demand forecasting models | Yes | Aggregate port preferred |
| *Example:* Marketing personalisation | Conditional | Only with a recorded legal basis in the records of processing |
| *Example:* Re-identifying customers by joining identity tables | No | Requires a separate privacy assessment |
| [Purpose] | [Yes / No / Conditional] | [Condition] |

### 8.4 Internal pricing and cost

If your organisation charges data products back to consumers, record the price in the output port contract. ODCS v3.2.0 models it with `price.priceAmount`, `price.priceCurrency` and `price.priceUnit`. Use this table for the summary and the rationale.

| Port | Price model | Amount | Unit | Rationale |
|---|---|---|---|---|
| *Example:* `orders` | No charge | 0 | n/a | Tier-1 shared product funded centrally |
| *Example:* `orders_daily_agg` | No charge | 0 | n/a | Small aggregate |
| [Port] | [Model] | [Amount and currency] | [Unit] | [Rationale] |

Report the product's running cost (compute, storage, egress) monthly to the domain data owner, even when no charge is made, so that the lifecycle review in section 11 can judge value against cost.

## 9. AI usage context

<!-- Guidance: Tell AI agents, assistants and semantic-layer tools how to use the product and what they must not do. Questions: which questions can be answered reliably, which joins or filters are mandatory, what must never be inferred? Pitfall: exposing a product to an assistant without any constraints, then discovering it answers revenue questions from the wrong port. -->

ODPS v1.1.0 adds an optional `context` block at product level and at output port level, with three parts: `instructions` (natural-language guidance scoped to that level), `verifiedStatements` (canonical business questions, optionally with a curated answer) and `constraints` (negative guidance, things an agent must not do). Input ports rely on the context in the linked contract instead.

### 9.1 Instructions

*Example:* "Use `orders_daily_agg` for any question about order counts or net revenue by day, channel or currency. Use `orders` version 2.1.0 only when order-level detail is required. Net revenue excludes tax and shipping. Always state the currency; never sum across currencies without the converted column."

### 9.2 Verified statements

| ID | Question | Curated answer or approach | Port |
|---|---|---|---|
| VS-001 *(Example)* | How many orders were confirmed yesterday by channel? | Sum `order_count` for the previous date grouped by `channel` | `orders_daily_agg` |
| VS-002 *(Example)* | What was net revenue last month in EUR? | Sum `net_amount_eur` for the month; cite the metric definition "Net revenue" | `orders_daily_agg` |
| VS-003 *(Example)* | What share of orders came from the app this quarter? | Divide app `order_count` by total `order_count` for the quarter | `orders_daily_agg` |
| [VS-nnn] | [Question] | [Answer or approach] | [Port] |

### 9.3 Constraints

| ID | Constraint |
|---|---|
| AIC-001 *(Example)* | Do not join `customer_id` to any identity table or attempt to identify a customer |
| AIC-002 *(Example)* | Do not report figures for days where the quality status is "provisional" without saying so |
| AIC-003 *(Example)* | Do not use the deprecated port `orders` version 1.4.0 (OUT-003); its `net_amount` is gross of discounts |
| AIC-004 *(Example)* | Do not answer questions about refunds; refer to the Returns product |
| [AIC-nnn] | [Constraint] |

Treat the context block as guidance for tools, not as an access control. Enforcement of who may see what remains in the roles in section 8 and the masking rules in the contracts.

## 10. Support and communication

<!-- Guidance: Give consumers one place to ask, one to report issues and one to receive announcements. Pitfall: personal chat messages to an engineer as the only support route. -->

ODPS support entries need a `channel` and a `url`, and may carry a `tool`, a `scope` (`interactive`, `announcements` or `issues`) and an invitation URL.

| ID | Channel | Tool | Scope | URL | Hours |
|---|---|---|---|---|---|
| SUP-001 *(Example)* | `#orders-data` | slack | interactive | [https://chat.example.internal/orders-data] | Business hours, CET |
| SUP-002 *(Example)* | Orders data issues | ticket | issues | [https://tickets.example.internal/orders] | 24x7 for SEV1 and SEV2 |
| SUP-003 *(Example)* | `orders-data-announce` | email | announcements | [mailto:orders-data-announce@example.com] | Not applicable |
| [SUP-nnn] | [channel] | [tool] | [scope] | [URL] | [hours] |

Every change notice, deprecation notice and incident update goes to the announcements channel. Consumers in the register (section 1.2) are subscribed when access is granted.

## 11. Lifecycle, versioning and deprecation

<!-- Guidance: Define the states the product passes through, how versions are numbered and how a port or the whole product is retired. Questions: how much notice do consumers get, how long do versions run in parallel, who decides to retire? Pitfall: a deprecated port that never actually goes away. -->

### 11.1 Lifecycle states

The ODPS `status` field suggests the values proposed, draft, active, deprecated and retired. The `deprecated` boolean exists on the product and on each port, so a single port can be deprecated while the product stays active.

```mermaid
stateDiagram-v2
  [*] --> proposed
  proposed --> draft: owner and domain agreed
  draft --> active: contracts approved, catalogue entry published
  active --> active: minor or patch release
  active --> deprecated: replacement available or no value
  deprecated --> retired: sunset date reached, consumers migrated
  retired --> [*]
```

### 11.2 Versioning

Product and ports use semantic versioning.

| Change | Version impact | Example |
|---|---|---|
| Breaking change to any output port contract | Major for that port; minor for the product | Renaming a column, changing grain, lowering an objective |
| New output port | Minor for the product | Adding `orders_daily_agg` |
| Compatible change to a port | Minor for the port | Adding a nullable column |
| Documentation or metadata only | Patch | Fixing a description or adding a synonym |
| Removing an output port | Major for the product | Retiring `orders` 1.4.0 (OUT-003) |

### 11.3 Deprecation and sunset rules

| Rule | Policy |
|---|---|
| Minimum notice before a breaking change | *Example:* 30 days, announced on SUP-003 and to each registered consumer |
| Parallel run of old and new port versions | *Example:* At least 60 days |
| Deprecation marker | Set `deprecated: true` on the port and the reason in its description |
| Sunset date | Recorded in the port register and in the announcement |
| Consumer migration tracking | Owner tracks each registered consumer to "migrated" or "accepted risk" |
| Retirement | Remove access roles, archive or delete data per the Data Retention and Deletion Standard (`data/data-retention-deletion-standard`) |

### 11.4 Product review

Review each active product at least once a year: consumers and usage, objectives met, open incidents, running cost and whether the product still has an owner. A product with no active consumers for two review cycles is a candidate for deprecation. Record the decision in the revision history.

## 12. Security, privacy and compliance

<!-- Guidance: Summarise how the product protects data and meets legal duties, and link the detailed assessments. Pitfall: copying controls from the platform document without checking they apply to this product's columns. -->

| Topic | Position |
|---|---|
| Highest classification of any column | *Example:* Confidential (`net_amount`); personal data limited to pseudonymous `customer_id` |
| Classification source | Data Classification Policy (`data/data-classification-policy`) |
| Personal data and legal basis | *Example:* Listed in the records of processing (`compliance/gdpr-ropa`), entry RPA-014 |
| Impact assessment | *Example:* DPIA not required after screening on YYYY-MM-DD; see `compliance/dpia` if the screening changes |
| De-identification | *Example:* `customer_id` is a keyed pseudonym; re-identification key held by the Customer domain |
| Retention | *Example:* Order grain 7 years, aggregate 10 years, per the retention schedule |
| Residency | *Example:* Stored and processed in EU regions only |
| Encryption | *Example:* At rest with platform-managed keys; in transit with TLS |
| Audit | *Example:* Query logs kept 1 year and reviewed quarterly for unusual access |

## 13. Traceability matrix

<!-- Guidance: Link consumers to ports, contracts, objectives and inputs so the impact of any change is visible in one place. Pitfall: letting this table drift from the YAML; regenerate it from the YAML when possible. -->

| Use case | Output port | Contract | Objectives | Input ports | AI context | Risk or note |
|---|---|---|---|---|---|---|
| UC-001 *(Example)* | OUT-001 | `corvid-orders-orders` | SLO-001, SLO-002, SLO-003 | IN-001, IN-003 | n/a | None |
| UC-002 *(Example)* | OUT-002 | `corvid-orders-orders-daily-agg` | SLO-004 | via OUT-001 | n/a | Model retraining depends on SLO-004 |
| UC-003 *(Example)* | OUT-001 | `corvid-orders-orders` | SLO-001 | IN-001, IN-002 | n/a | Uses pseudonymous `customer_id` |
| UC-004 *(Example)* | OUT-002 | `corvid-orders-orders-daily-agg` | SLO-004 | via OUT-001 | VS-001 to VS-003, AIC-001 to AIC-004 | None |
| UC-005 *(Example)* | OUT-001 | `corvid-orders-orders` | SLO-001, SLO-002 | IN-001 | n/a | Checkout depends on Orders, never the reverse |
| [UC-nnn] | [OUT-nnn] | [contract] | [SLO-nnn] | [IN-nnn] | [IDs] | [Note] |

## 14. ODPS YAML skeleton

<!-- Guidance: Keep this file next to the prose and validate it against the ODPS v1.1.0 JSON schema in CI. Only `apiVersion`, `kind` and `id` are required by the schema; fill the rest anyway, because the catalogue is only as useful as its metadata. Pitfall: mixing in fields from the opendataproducts.org specification, which the Bitol schema rejects. -->

The skeleton below uses field names from the Bitol ODPS v1.1.0 JSON schema. Values are the Corvid Commerce examples; replace them. Keep the schema, quality, service level and price detail in the referenced ODCS contract files.

```yaml
apiVersion: v1.1.0
kind: DataProduct
id: 7d1c3e52-0f4b-4a8e-9c61-2b5e8f0a9d10   # stable identifier (example)
name: Orders
version: 2.1.0
status: active
domain: sales
type: sourceAligned
tenant: corvid-commerce
deprecated: false
productCreatedTs: "2025-03-01T09:00:00Z"
description:
  purpose: One conformed record of every confirmed customer order across channels.
  usage: Revenue and volume analytics at order or daily grain; demand forecasting features.
  limitations: Up to 60 minutes old; marketplace orders complete within 24 hours; not for statutory reporting.
synonyms:
  - synonym: sales orders
    source: glossary
    status: active
  - synonym: commandes
    locale: fr-FR
    source: finance-team
tags: [sales, orders, revenue, tier-1, PD, PSEUDO]
authoritativeDefinitions:
  - type: businessDefinition
    url: https://glossary.example.internal/terms/order
  - type: productSpecification
    url: https://git.example.internal/data/orders/blob/main/docs/data-product-specification.md
customProperties:
  - property: costCentre
    value: CC-4410
  - property: certificationLevel
    value: certified
inputPorts:
  - name: order_events
    version: 3.0.0
    contractId: corvid-ordersvc-order-events
  - name: products_v3
    version: 3.2.0
    contractId: corvid-products-products
  - name: fx_rates
    version: 1.1.0
    contractId: corvid-fx-fx-rates
outputPorts:
  - name: orders
    version: 2.1.0
    type: tables
    description: One row per confirmed order, conformed across channels.
    contractId: corvid-orders-orders
    inputContracts:
      - id: corvid-ordersvc-order-events
        version: 3.0.0
      - id: corvid-fx-fx-rates
        version: 1.1.0
    sbom:
      - type: external
        url: https://git.example.internal/data/orders/releases/2.1.0/sbom.cdx.json
  - name: orders_daily_agg
    version: 1.0.0
    type: tables
    description: Daily order count and net revenue by channel and currency.
    contractId: corvid-orders-orders-daily-agg
    inputContracts:
      - id: corvid-orders-orders
        version: 2.1.0
    context:
      instructions: >-
        Use this port for order counts and net revenue by day, channel or currency.
        Always state the currency.
      verifiedStatements:
        - question: How many orders were confirmed yesterday by channel?
          answer: Sum order_count for the previous date grouped by channel.
      constraints:
        - constraint: Do not report provisional days without saying they are provisional.
  - name: orders
    version: 1.4.0
    type: tables
    description: Previous order grain port, kept for migration until 2026-12-31.
    contractId: corvid-orders-orders
    deprecated: true
managementPorts:
  - name: catalogue-metadata
    content: discoverability
    type: rest
    url: https://catalogue.example.internal/api/products/orders
  - name: orders-health
    content: observability
    type: rest
    url: https://observability.example.internal/orders
  - name: orders-lineage-events
    content: observability
    type: topic
    channel: lineage.orders
  - name: orders-control
    content: control
    type: rest
    url: https://platform.example.internal/products/orders/control
support:
  - channel: orders-data
    tool: slack
    scope: interactive
    url: https://chat.example.internal/orders-data
  - channel: orders-data-issues
    tool: ticket
    scope: issues
    url: https://tickets.example.internal/orders
  - channel: orders-data-announce
    tool: email
    scope: announcements
    url: mailto:orders-data-announce@example.com
team:
  name: Sales Data Products
  description: Owns the Orders and Checkout data products.
  members:
    - username: a.okafor
      role: owner
      dateIn: "2025-03-01"
    - username: m.lindqvist
      role: data steward
      dateIn: "2025-03-01"
    - username: j.kim
      role: data engineer
      dateIn: "2025-06-15"
context:
  instructions: >-
    Prefer orders_daily_agg for aggregate questions. Net revenue excludes tax
    and shipping. Never sum across currencies without the converted column.
  constraints:
    - constraint: Do not join customer_id to any identity table.
    - constraint: Do not use the deprecated port orders version 1.4.0.
```

Validate the file in CI against the published JSON schema for the version named in `apiVersion`. The schema file names in the Bitol repository follow the pattern `odps-json-schema-v1.1.0.json`; pin the exact version rather than "latest" so an upstream change cannot break the build silently. Values such as the output port `type` and support `tool` are free text in v1.1.0; agree a controlled list in the standards register.

## 15. Glossary and references

<!-- Guidance: Define terms consumers may not share and list every standard you relied on with the version checked. -->

### 15.1 Glossary

| Term | Meaning in this document |
|---|---|
| Data product | A unit of data, code, metadata and contracts owned by a domain team and offered to consumers through defined ports |
| Input port | A declared dependency on a source, governed by that source's contract |
| Output port | A versioned interface through which consumers read the product, governed by a contract |
| Management port | An interface for discovering, observing or controlling the product |
| Data contract | Agreement between producer and consumers on schema, quality, service levels and terms for one port |
| Source aligned | A product shaped closely to one operational source |
| Aggregate | A product that combines several sources |
| Consumer aligned | A product shaped for one family of uses |
| Verified statement | A canonical business question with an approved answer or approach, used to guide AI tools |
| Sunset date | The date after which a deprecated port is removed |

### 15.2 References

| Reference | Version or date checked | Use |
|---|---|---|
| Bitol Open Data Product Standard (ODPS), github.com/bitol-io/open-data-product-standard | v1.1.0, released 2026-09-08; checked 2026-10-24 | Field names, port model, context block, synonyms, deprecation flag |
| Bitol Open Data Contract Standard (ODCS), github.com/bitol-io/open-data-contract-standard | v3.2.0, released 2026-09-08; checked 2026-10-24 | Contract fields for service levels (`slaProperties`) and price |
| Open Data Product Specification, opendataproducts.org | 4.1, dated 2025-10-24; checked 2026-10-24 | Named only, to distinguish it from the Bitol standard |
| OpenLineage specification, openlineage.io | Checked 2026-10-24 | Lineage events on the observability port |
| Data Mesh (Zhamak Dehghani, O'Reilly, 2022) | Book, cited by name | Background for data as a product; no text used |
| [Internal catalogue documentation] | [Version] | [Custom properties and controlled lists] |

## Standard and licence note

Based on the Bitol Open Data Product Standard v1.1.0 and the Bitol Open Data Contract Standard v3.2.0, both published by the Bitol project under LF AI and Data and licensed under the Apache License 2.0 (github.com/bitol-io). Field names and allowed values are used as published so the YAML validates against the official schema; all explanatory text, tables and examples are original to this template. The Open Data Product Specification at opendataproducts.org is a separate specification and is named only for disambiguation. The Data Mesh book is cited by name only. Corvid Commerce is fictional. Check the current official version of each specification and its JSON schema before use. Template licence: MIT.
