include::{mod-loc}shared/all-attributes.adoc[]

[id="data-contracts_{context}"]
= {registry} data contracts

[role="_abstract"]
This chapter introduces the Data Contracts framework in {registry}, which uses the Open Data Contract Standard (ODCS) v3.1 as the native contract format for managing schema contracts between data producers and consumers.

* xref:data-contracts-overview_{context}[]
* xref:data-contracts-odcs_{context}[]
* xref:data-contracts-artifact-references_{context}[]
* xref:data-contracts-field-tags_{context}[]
* xref:data-contracts-rules_{context}[]
* xref:data-contracts-lifecycle_{context}[]
* xref:data-contracts-migration_{context}[]
* xref:data-contracts-serdes_{context}[]
* xref:data-contracts-audit_{context}[]
* xref:data-contracts-search_{context}[]
* xref:data-contracts-global-rules_{context}[]
* xref:data-contracts-api_{context}[]
* xref:data-contracts-configuration_{context}[]

[id="data-contracts-overview_{context}"]
== Overview

[role="_abstract"]
The Data Contracts framework enables teams to define, enforce, and track formal agreements about schema structure, ownership, and quality using the industry-standard ODCS format.

A data contract in {registry} combines:

* *ODCS format* - Contracts are submitted as ODCS v3.1 YAML documents and projected onto schema artifacts
* *Metadata* - Ownership, classification, SLA, and support information projected as labels
* *Lifecycle* - Status tracking (DRAFT, STABLE, DEPRECATED) and promotion stages (DEV, STAGE, PROD)
* *Quality rules* - Accuracy and completeness rules from ODCS projected as CEL contract rules
* *Field tags* - PII and classification annotations stored as version labels, with automatic extraction from Avro, JSON Schema, and Protobuf schemas
* *Multi-contract support* - Multiple contracts can reference the same schema artifact without overwriting each other

NOTE: Data contracts are always available. No special configuration is required — submitting a contract is an explicit opt-in action.

[id="data-contracts-odcs_{context}"]
== ODCS native format

[role="_abstract"]
{registry} uses the Open Data Contract Standard (ODCS) v3.1 as its native contract format. ODCS is a Linux Foundation standard maintained by the Bitol project. When an ODCS contract is submitted, its contents are *projected* onto the referenced schema artifacts.

=== Why ODCS

* *Industry standard* - ODCS is the Linux Foundation standard for data contracts, with ecosystem tooling such as `datacontract-cli`
* *Vendor neutral* - {registry} is the first schema registry to natively support ODCS
* *Interoperable* - ODCS contracts can be imported/exported and used with external tools without translation

=== Projection model

When you submit an ODCS contract, {registry} parses the YAML, stores it as an `ODCS_CONTRACT` artifact, and projects its contents onto the referenced schema artifacts. Each projection is namespaced by the contract ID, so multiple contracts can coexist on the same schema.

.ODCS projection mapping
[cols="2,3", options="header"]
|===
|ODCS section |Projected to

|`info.status`
|`contract.{contractId}.status` label (draft->DRAFT, active->STABLE, deprecated->DEPRECATED)

|`info.dataClassification`
|`contract.{contractId}.classification` label

|`team.name` / `team.domain` / `team.contact`
|`contract.{contractId}.owner.team`, `.owner.domain`, `.support.contact` labels

|`quality.accuracy` rules
|CEL contract rules prefixed `odcs:{contractId}:` in the contract_rules table

|`quality.completeness` rules
|`contract.{contractId}.quality.completeness.{field}` labels

|`quality.freshness`
|`contract.{contractId}.quality.freshness.maxStaleness` label

|`schemas[].fields[].pii` / `.tags`
|`field-tag.{contractId}:{fieldPath}\|{tagName}` version labels

|`serviceLevel.*`
|`contract.{contractId}.sla.*` labels (stored, not enforced by registry)
|===

=== Multi-contract support

Multiple ODCS contracts can reference the same schema artifact. Each contract's projections are namespaced by contract ID, so they do not interfere:

----
Contract "orders-contract" → contract.orders-contract.owner.team = orders-team
Contract "billing-contract" → contract.billing-contract.owner.team = billing-team
----

Updating or deleting a contract only affects its own namespaced labels, rules, and tags.

=== ODCS contract example

.ODCS v3.1 contract
[source,yaml]
----
apiVersion: v3.1.0
kind: DataContract
id: orders-contract
info:
  title: Orders Contract
  version: 1.0.0
  description: Data contract for order events
  status: active
  dataClassification: confidential
team:
  name: orders-team
  domain: commerce
  contact: orders@company.com
schemas:
  - name: OrderEvent
    type: avro
    location: orders/OrderEvent:3
    fields:
      customerEmail:
        description: Customer email address
        pii: true
        tags:
          - PII
          - EMAIL
      totalAmount:
        description: Total order amount in cents
quality:
  accuracy:
    - name: positive-amount
      expression: totalAmount > 0
      threshold: 1.0
    - name: valid-email
      expression: "customerEmail.matches('.*@.*\\\\..*')"
      threshold: 0.99
  completeness:
    - field: orderId
      threshold: 1.0
    - field: customerEmail
      threshold: 0.99
  freshness:
    maxStaleness: PT5M
serviceLevel:
  availability: 0.999
  latency:
    p99: PT1S
----

When submitted, this contract:

1. Creates an `ODCS_CONTRACT` artifact storing the YAML
2. Sets namespaced labels on the `orders/OrderEvent` artifact: `contract.orders-contract.status=STABLE`, `contract.orders-contract.owner.team=orders-team`, `contract.orders-contract.classification=CONFIDENTIAL`
3. Creates two CEL contract rules (`odcs:orders-contract:positive-amount` with onFailure=ERROR, `odcs:orders-contract:valid-email` with onFailure=DLQ)
4. Stores completeness thresholds as labels (`contract.orders-contract.quality.completeness.orderId=1.0`)
5. Sets field tags: `field-tag.orders-contract:customerEmail|PII=EXTERNAL`, `field-tag.orders-contract:customerEmail|EMAIL=EXTERNAL`
6. Stores SLA labels for external monitoring tools

=== Quality rule threshold mapping

ODCS accuracy rule thresholds determine what happens when a rule fails:

* *1.0 (100%)* -> `ERROR` - Zero tolerance, reject non-compliant data
* *0.95-0.99* -> `DLQ` - Near-100%, route violations to dead letter queue
* *Below 0.95* -> `NONE` - Soft constraint, log only

=== Manual rules are preserved

When you update an ODCS contract, only rules prefixed with `odcs:{contractId}:` are replaced. Rules added manually through the contract ruleset API are preserved.

=== Schema location format

The `schemas[].location` field references an artifact in the registry:

----
groupId/artifactId:version
----

Examples:

* `orders/OrderEvent:3` - group "orders", artifact "OrderEvent", version "3"
* `OrderEvent:latest` - default group, artifact "OrderEvent", latest version
* `OrderEvent` - default group, artifact "OrderEvent", latest version

IMPORTANT: Each entry under `schemas[]` is independent. Projection and field metadata (PII, tags, classifications) apply only to the artifact named in that entry's `location`. Cross-artifact schema references inside the Avro/JSON Schema/Protobuf content are not followed during projection. If a nested type lives in another artifact, either add a separate `schemas[]` entry for that artifact or give it its own contract. See xref:data-contracts-artifact-references_{context}[].

[id="data-contracts-artifact-references_{context}"]
== Artifact references and data contracts

[role="_abstract"]
Schema artifacts can reference other artifacts (for example an Avro `OrderEvent` that references a shared `Address` record). Data contracts stay scoped to each artifact. {registry} does not walk those cross-artifact references when projecting ODCS field metadata.

=== What works at evaluation time

When a Kafka record is serialized, nested objects are fully materialized. An Avro `GenericRecord` contains the nested `Address` values inline, not a pointer to another registry artifact. After conversion to a map, CEL rules can reach nested paths:

[source,json]
----
{
  "orderId": "ORD-123",
  "address": { "street": "123 Main St", "zipCode": "90210" }
}
----

A CEL expression such as `address.zipCode != ""` works at rule evaluation time because the nested fields are present on the record.

=== What projection does not do

ODCS `schemas[].fields` metadata (PII flags, tags, classifications) is projected only for fields that belong to the artifact listed in that `schemas[]` entry. The projection engine does not resolve artifact references to discover fields that live on another schema.

So you cannot tag `address.street` as PII from an `OrderEvent` contract if `street` is defined on a separate `Address` artifact. Declare PII on the `Address` contract (or add `Address` as its own `schemas[]` entry) instead.

=== Why contracts stay per-artifact

* Ownership stays clear: the team that owns `Address` declares which of its fields are PII.
* Changing `Address` does not force every dependent contract to be re-projected.
* Conflicting metadata is avoided when two contracts would otherwise both claim the same nested field.

=== Practical guidance

* Put field-level PII and tags on the contract (or `schemas[]` entry) for the artifact that actually defines those fields.
* Use CEL quality rules on the parent artifact when you need to validate nested values on the wire; those rules see the materialized record.
* List every governed schema under `schemas[]` when one ODCS document should cover several related artifacts.

NOTE: **Upgrade note:** Earlier releases projected field tags from every `schemas[]` entry onto each target artifact. Field-tag projection now uses only the entry whose `location` resolves to the target artifact. Artifacts with no matching entry receive no projected field tags. Contracts that previously relied on cross-entry tag bleed-through should add a dedicated `schemas[]` entry per governed artifact.

[id="data-contracts-field-tags_{context}"]
== Field-level tags

[role="_abstract"]
Field tags provide semantic annotation of schema fields, enabling you to identify sensitive data and apply tag-based governance.

=== Tag sources

Tags come from two sources:

* *Inline tags* - Extracted automatically from schema content during registration by tag extractors
* *External tags* - Projected from ODCS contracts or set manually via the version labels API

=== Supported schema formats

Tag extractors are available for the following formats:

==== Avro

Tags are extracted from `tags` and `confluent:tags` field properties:

[source,json]
----
{
  "type": "record",
  "name": "User",
  "fields": [{
    "name": "email",
    "type": "string",
    "tags": ["PII", "EMAIL"]
  }]
}
----

==== JSON Schema

Tags are extracted from `x-tags` and `x-confluent-tags` extension properties:

[source,json]
----
{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "x-tags": ["PII", "EMAIL"]
    }
  }
}
----

Supports nested objects, arrays, `additionalProperties`, and `allOf`/`oneOf`/`anyOf` compositions.

==== Protobuf

Tags are extracted from documentation comments using the `@tag:` annotation:

[source,protobuf]
----
message User {
  // @tag:PII,EMAIL
  string email = 1;
}
----

Also supports Confluent `confluent.field_meta` field options.

=== Common tags

* `PII` - Personally Identifiable Information
* `SENSITIVE` - Sensitive data requiring special handling
* `EMAIL` - Email address fields
* `PHONE` - Phone number fields
* `FINANCIAL` - Financial data
* `HEALTH` - Health-related data (PHI)

[id="data-contracts-rules_{context}"]
== Contract rules

[role="_abstract"]
Contract rules define validation and transformation logic associated with artifacts or specific versions. Rules are organized into *domain rules* (business logic) and *migration rules* (schema evolution).

When you submit an ODCS contract, its `quality.accuracy` rules are automatically projected as CEL domain rules on the referenced schema artifact.

Contract rules are enforced both server-side (via the rule execution REST endpoint) and client-side (via Java Kafka SerDes integration). See xref:data-contracts-serdes_{context}[] for SerDes configuration.

=== Rule kinds

* *CONDITION* - Validates a condition, passes or fails
* *TRANSFORM* - Modifies data

=== Rule modes

* *WRITE* - Execute on serialize (producer-side)
* *READ* - Execute on deserialize (consumer-side)
* *WRITEREAD* - Execute on both
* *UPGRADE* / *DOWNGRADE* - Execute during schema migration

=== Rule types

* *CEL* - Common Expression Language for validation conditions and simple transforms. Supports full CEL spec including `has()`, `size()`, `contains()`, `startsWith()`, parenthesized grouping, and ternary expressions.
* *JSONATA* - JSONata expressions for data transformation. Supports both CONDITION (truthy check) and TRANSFORM (returns transformed data) kinds. Useful for schema migration transforms.

=== Rule actions

* *NONE* - Continue processing (log only)
* *ERROR* - Throw an exception and reject the operation
* *DLQ* - Route the message to a dead letter queue

[id="data-contracts-lifecycle_{context}"]
== Contract lifecycle

[role="_abstract"]
The contract lifecycle tracks the maturity of a schema through defined status transitions.

=== Statuses

* *DRAFT* - The schema is being developed and is not ready for production
* *STABLE* - The schema is production-ready
* *DEPRECATED* - The schema is being phased out

=== Status transitions

----
DRAFT ──────► STABLE ──────► DEPRECATED
   │                              ▲
   └──────────────────────────────┘
         (skip stable)
----

NOTE: Reverse transitions are not allowed.

=== Promotion stages

----
DEV ──────► STAGE ──────► PROD
----

Promotion is enforced via the `POST /groups/{g}/artifacts/{a}/contract/promote` endpoint, which validates that the target stage follows the allowed progression. The promotion stage can also be set directly via the contract metadata API.

[id="data-contracts-migration_{context}"]
== Schema migration

[role="_abstract"]
Schema migration allows you to transform data records between different schema versions using migration rules.

=== Migration rules

Migration rules use the UPGRADE or DOWNGRADE mode and are typically JSONata TRANSFORM rules that reshape data from one version to another:

.Migration rule example
[source,json]
----
{
  "domainRules": [],
  "migrationRules": [
    {
      "name": "v1-to-v2-upgrade",
      "kind": "TRANSFORM",
      "type": "JSONATA",
      "mode": "UPGRADE",
      "expr": "$ ~> |$|{'fullName': firstName & ' ' & lastName}|",
      "onFailure": "ERROR",
      "disabled": false
    }
  ]
}
----

=== Migration endpoint

The migration endpoint chains transforms across multiple versions:

[source,bash]
----
curl -X POST \
  http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/migrate \
  -H 'Content-Type: application/json' \
  -d '{
    "sourceVersion": "1",
    "targetVersion": "3",
    "record": {"firstName": "John", "lastName": "Doe", "amount": 100}
  }'
----

The service determines the direction (upgrade or downgrade), loads migration rules for each intermediate version, and chains the transforms sequentially.

=== Compatibility groups

Compatibility groups partition the version history for compatibility checking. Versions in different groups are not compared against each other during compatibility validation.

[source,bash]
----
# Set compatibility group
curl -X PUT \
  http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/compatibility-group \
  -H 'Content-Type: application/json' \
  -d '{"group": "v2-family"}'

# Get compatibility group
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/compatibility-group
----

[id="data-contracts-serdes_{context}"]
== Kafka SerDes integration

[role="_abstract"]
Contract rules can be enforced during normal Kafka serialization and deserialization using the Apicurio Registry SerDes libraries. Rules are executed transparently — no code changes are required beyond configuration.

=== Configuration

Enable contract rule enforcement by setting these properties on your Kafka producer or consumer:

.SerDes contract rule properties
[cols="3,1,3", options="header"]
|===
|Property |Default |Description

|`apicurio.registry.contract-rules.enabled`
|`false`
|Enable contract rule execution during serialize/deserialize

|`apicurio.registry.contract-rules.fail-on-error`
|`true`
|When `true`, rule violations throw a `RuntimeException`. When `false`, violations are logged but serialization proceeds (for DLQ routing by the application)
|===

=== How it works

* *Producer (serializer):* After schema resolution and before serialization, the serializer calls the server-side rule execution endpoint with mode `WRITE`. If any CONDITION rule with `onFailure=ERROR` fails, serialization is rejected.
* *Consumer (deserializer):* After deserialization, the deserializer calls the rule execution endpoint with mode `READ`. This enables read-side validation rules.
* *DLQ pattern:* Set `fail-on-error=false` to allow serialization to proceed even when rules fail. Your application can inspect the violations and route the message to a dead letter queue.

=== Example: Producer with contract rules

[source,java]
----
Properties props = new Properties();
props.put(SerdeConfig.REGISTRY_URL, "http://localhost:8080/apis/registry/v3");
props.put(SerdeConfig.EXPLICIT_ARTIFACT_GROUP_ID, "my-group");
props.put(SerdeConfig.ARTIFACT_RESOLVER_STRATEGY, TopicRecordIdStrategy.class.getName());
props.put(SerdeConfig.CONTRACT_RULES_ENABLED, "true");
props.put(SerdeConfig.CONTRACT_RULES_FAIL_ON_ERROR, "true");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, AvroKafkaSerializer.class.getName());
// ... other Kafka producer properties

KafkaProducer<String, GenericRecord> producer = new KafkaProducer<>(props);
----

[id="data-contracts-audit_{context}"]
== Audit log

[role="_abstract"]
The contract audit log records all contract operations for compliance and debugging.

=== Audited operations

* Metadata updates (owner, classification, status changes)
* Ruleset configuration (create, update, delete)
* Status transitions (DRAFT → STABLE → DEPRECATED)
* Contract submission and re-projection

=== Querying the audit log

[source,bash]
----
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/audit
----

.Response
[source,json]
----
[
  {
    "action": "STATUS_TRANSITION",
    "detail": "Status changed from DRAFT to STABLE",
    "userId": "admin",
    "createdOn": "2026-05-19T10:30:00Z"
  },
  {
    "action": "RULESET_CONFIGURED",
    "detail": "Contract ruleset updated",
    "userId": "admin",
    "createdOn": "2026-05-19T10:25:00Z"
  }
]
----

[id="data-contracts-search_{context}"]
== Contract search

[role="_abstract"]
Search for contracts across the registry using metadata filters.

=== Search parameters

.Contract search query parameters
[cols="2,3", options="header"]
|===
|Parameter |Description

|`status`
|Filter by lifecycle status (DRAFT, STABLE, DEPRECATED)

|`ownerTeam`
|Filter by owner team name

|`classification`
|Filter by data classification (e.g., CONFIDENTIAL, PUBLIC)

|`groupId`
|Filter by group

|`artifactId`
|Filter by artifact

|`limit` / `offset`
|Pagination (default limit: 20, max: 500)

|`orderby` / `order`
|Sort by field and direction
|===

=== Example

[source,bash]
----
curl "http://localhost:8080/apis/registry/v3/search/contracts?status=STABLE&ownerTeam=orders-team&limit=10"
----

[id="data-contracts-global-rules_{context}"]
== Global contract rules

[role="_abstract"]
Global contract rulesets provide a baseline set of rules applied to all artifacts. Artifact-level and version-level rules override global rules by name.

=== Rule precedence

When rules are executed, they are merged from three scopes:

1. *Global rules* — apply to all artifacts (set via `/admin/contracts/ruleset`)
2. *Artifact rules* — apply to a specific artifact (set via `/groups/{g}/artifacts/{a}/contract/ruleset`)
3. *Version rules* — apply to a specific version (set via `.../versions/{v}/contract/ruleset`)

If a rule with the same name exists at multiple levels, the most specific scope wins.

=== Managing global rules

[source,bash]
----
# Set global ruleset
curl -X PUT \
  http://localhost:8080/apis/registry/v3/admin/contracts/ruleset \
  -H 'Content-Type: application/json' \
  -d '{
    "domainRules": [{
      "name": "require-non-empty",
      "kind": "CONDITION",
      "type": "CEL",
      "mode": "WRITE",
      "expr": "size(record) > 0",
      "onFailure": "ERROR",
      "disabled": false
    }],
    "migrationRules": []
  }'

# Get global ruleset
curl http://localhost:8080/apis/registry/v3/admin/contracts/ruleset

# Delete global ruleset
curl -X DELETE http://localhost:8080/apis/registry/v3/admin/contracts/ruleset
----

[id="data-contracts-api_{context}"]
== REST API

[role="_abstract"]
The Data Contracts REST API provides endpoints for managing ODCS contracts, contract metadata, and contract rulesets.

=== ODCS contract endpoints

.ODCS contract REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|POST
|`/groups/{groupId}/contracts`
|Submit an ODCS contract YAML. Creates the contract artifact and projects onto referenced schema artifacts.

|GET
|`/groups/{groupId}/contracts?limit=&offset=`
|List ODCS contracts in a group (paginated, default 20, max 500)

|GET
|`/groups/{groupId}/contracts/{contractId}`
|Retrieve the ODCS contract YAML

|PUT
|`/groups/{groupId}/contracts/{contractId}`
|Update an ODCS contract (creates a new version and re-projects)

|DELETE
|`/groups/{groupId}/contracts/{contractId}`
|Delete an ODCS contract artifact

|GET
|`/groups/{groupId}/artifacts/{artifactId}/contract/export`
|Export an artifact's contract data as ODCS YAML
|===

=== Contract metadata endpoints

.Contract metadata REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|GET
|`/groups/{groupId}/artifacts/{artifactId}/contract/metadata`
|Get contract metadata for an artifact

|PUT
|`/groups/{groupId}/artifacts/{artifactId}/contract/metadata`
|Create or update contract metadata
|===

=== Contract ruleset endpoints

.Contract ruleset REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|GET
|`/groups/{groupId}/artifacts/{artifactId}/contract/ruleset`
|Get the contract ruleset for an artifact

|PUT
|`/groups/{groupId}/artifacts/{artifactId}/contract/ruleset`
|Create or replace the contract ruleset

|DELETE
|`/groups/{groupId}/artifacts/{artifactId}/contract/ruleset`
|Delete the contract ruleset
|===

Version-level rulesets are also available at `.../versions/{version}/contract/ruleset`.

=== Contract status transition endpoint

.Contract status transition endpoint
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|POST
|`/groups/{groupId}/artifacts/{artifactId}/contract/status`
|Change lifecycle status with transition validation
|===

=== Governance and quality endpoints

.Governance REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|POST
|`/groups/{groupId}/artifacts/{artifactId}/contract/promote`
|Promote contract stage (DEV -> STAGE -> PROD). Body: `{"contractId": "...", "targetStage": "STAGE"}`

|GET
|`/groups/{groupId}/artifacts/{artifactId}/contract/quality?contractId=`
|Get quality score (overall, completeness, compliance, stability)
|===

=== Rule execution and migration endpoints

.Rule execution REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|POST
|`/groups/{groupId}/artifacts/{artifactId}/versions/{version}/contract/execute`
|Execute contract rules against a data record. Body: `{"mode": "WRITE", "record": {...}}`

|POST
|`/groups/{groupId}/artifacts/{artifactId}/contract/migrate`
|Migrate a record between versions. Body: `{"sourceVersion": "1", "targetVersion": "3", "record": {...}}`
|===

=== Audit log endpoint

.Contract audit log REST API endpoint
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|GET
|`/groups/{groupId}/artifacts/{artifactId}/contract/audit`
|Get contract audit log entries for an artifact
|===

=== Compatibility group endpoints

.Compatibility group REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|GET
|`/groups/{groupId}/artifacts/{artifactId}/contract/compatibility-group`
|Get the compatibility group for an artifact

|PUT
|`/groups/{groupId}/artifacts/{artifactId}/contract/compatibility-group`
|Set the compatibility group for an artifact
|===

=== Contract search endpoint

.Contract search REST API endpoint
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|GET
|`/search/contracts?status=&ownerTeam=&classification=&limit=&offset=`
|Search contracts across the registry
|===

=== Global contract ruleset endpoints

.Global contract ruleset REST API endpoints
[cols="1,4,3", options="header"]
|===
|Method |Endpoint |Description

|GET
|`/admin/contracts/ruleset`
|Get the global contract ruleset

|PUT
|`/admin/contracts/ruleset`
|Set the global contract ruleset

|DELETE
|`/admin/contracts/ruleset`
|Delete the global contract ruleset
|===

Uses real CEL (Common Expression Language) via the `cel-standalone` library. Full CEL spec support including parenthesized grouping, ternary expressions, string functions, `has()`, `size()`. JSONata is also supported for data transformation rules.

=== Example: Submitting an ODCS contract

[source,bash]
----
curl -X POST \
  http://localhost:8080/apis/registry/v3/groups/my-group/contracts \
  -H 'Content-Type: application/x-yaml' \
  --data-binary @my-contract.yaml
----

.Response
[source,json]
----
{
  "contractId": "orders-contract",
  "version": "1.0.0",
  "projection": {
    "rulesApplied": 2,
    "labelsApplied": 8,
    "tagsApplied": 3,
    "warnings": []
  }
}
----

=== Example: Listing contracts

[source,bash]
----
curl "http://localhost:8080/apis/registry/v3/groups/my-group/contracts?limit=10&offset=0"
----

=== Example: Exporting as ODCS

[source,bash]
----
curl http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/export
----

Returns the reconstructed ODCS v3.1 YAML from the artifact's current contract labels, rules, and field tags.

=== Example: Promoting a contract

[source,bash]
----
curl -X POST \
  http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/promote \
  -H 'Content-Type: application/json' \
  -d '{"contractId": "orders-contract", "targetStage": "STAGE"}'
----

=== Example: Getting quality score

[source,bash]
----
curl "http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/quality?contractId=orders-contract"
----

.Response
[source,json]
----
{"overall": 0.85, "completeness": 0.83, "compliance": 1.0, "stability": 0.67}
----

=== Example: Executing contract rules

[source,bash]
----
curl -X POST \
  http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/versions/1/contract/execute \
  -H 'Content-Type: application/json' \
  -d '{"mode": "WRITE", "record": {"message": {"totalAmount": 100, "status": "active"}}}'
----

.Response
[source,json]
----
{"passed": true, "violations": [], "executedRules": 2, "failedRules": 0}
----

=== Example: Setting contract metadata directly

[source,bash]
----
curl -X PUT \
  http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/metadata \
  -H 'Content-Type: application/json' \
  -d '{
    "status": "DRAFT",
    "ownerTeam": "Platform Team",
    "ownerDomain": "payments",
    "supportContact": "platform@example.com",
    "classification": "CONFIDENTIAL",
    "stage": "DEV"
  }'
----

=== Example: Transitioning contract status

[source,bash]
----
curl -X POST \
  http://localhost:8080/apis/registry/v3/groups/my-group/artifacts/my-artifact/contract/status \
  -H 'Content-Type: application/json' \
  -d '{ "status": "STABLE" }'
----

[id="data-contracts-configuration_{context}"]
== Configuration

[role="_abstract"]
Data contracts are always available — no special configuration is required. Submitting a contract is an explicit opt-in action that does not affect existing registry functionality.

=== SerDes configuration

.SerDes contract rule properties
[cols="3,1,3", options="header"]
|===
|Property |Default |Description

|`apicurio.registry.contract-rules.enabled`
|`false`
|Enable contract rule execution during serialize/deserialize

|`apicurio.registry.contract-rules.fail-on-error`
|`true`
|When `true`, rule violations throw `RuntimeException`. When `false`, violations are logged but serialization proceeds
|===

=== Storage compatibility

Data contracts work across all storage variants:

* *SQL (PostgreSQL)* — Full support with immediate consistency. The `contract_audit_log` table is created automatically (DB upgrade 106).
* *KafkaSQL* — Full support; label writes go through the Kafka journal for multi-node consistency
* *GitOps / KubernetesOps* — Read-only safe; write operations are rejected by the read-only decorator

[role="_additional-resources"]
.Additional resources
* {registry-rules}
* link:https://bitol.io/[Open Data Contract Standard (ODCS)]
* link:{attachmentsdir}/registry-rest-api.htm[Apicurio Registry REST API documentation]
