Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e2f6b1e
refactor(odcs): split provider into modular bidirectional mappers + a…
claude May 23, 2026
041b248
feat(odps_bitol): Phase 2 — bidirectional Bitol ODPS provider (export…
claude May 23, 2026
f0b1630
feat(odps_bitol): Phase 3 — contractId resolver + three import entry …
claude May 23, 2026
08563d2
feat(cli/opds): Phase 4 — --spec dispatcher + new import subcommand
claude May 23, 2026
4521ba4
refactor: Phase 5 — naming disambiguation across ODPS / ODPI / Bitol
claude May 23, 2026
8bde76c
test: Phase 6 — test pyramid for ODCS lossless + Bitol ODPS bidirecti…
claude May 23, 2026
c282777
feat(forge): Phase 7 — --seed-from + structural-seed pre-processor
claude May 23, 2026
33f7cc6
docs: Phase 8 — BITOL_INTEROP.md, README disambiguation, CHANGELOG
claude May 23, 2026
04fa2ca
chore: gitignore stray test-run artifact at repo root
claude May 23, 2026
667db68
fix(dmm): make `fluid datamesh-manager publish` work against Entropy …
claude May 23, 2026
da054e6
fix(publish): make `fluid publish --catalog datamesh-manager` work ou…
claude May 23, 2026
1ba0ea0
fix(catalogs): preserve full FLUID dict through CatalogAsset → DMM pu…
claude May 23, 2026
de4a7ea
feat(odcs): optional vowl second-pass validation on export
claude May 23, 2026
004c57f
chore: dead-code + overengineering cleanup pass
claude May 23, 2026
51ba005
test: parameterised forge-seed × opds-import × input-shape matrix
claude May 23, 2026
b4480d6
test(forge): real-LLM E2E for seed → ground-truth-guard → repair loop
claude May 23, 2026
b21e34f
refactor(catalogs): sync __init__.py — CatalogTarget in registry
fas89 Aug 29, 2026
5f572d2
refactor(catalogs): sync datamesh_manager.py — publish(fluid, target)
fas89 Aug 29, 2026
a5df6c2
refactor(catalogs): sync base.py — CatalogTarget + publish(fluid, tar…
fas89 Aug 29, 2026
90c926a
refactor(catalogs): sync fluid_cc/provider.py — publish(fluid, target)
fas89 Aug 29, 2026
375b7cd
refactor(cli/publish): sync publish.py — builds CatalogTarget from FLUID
fas89 Aug 29, 2026
71bf419
test(catalogs): sync DMM tests — new publish(fluid, target) signature
fas89 Aug 29, 2026
b1b01b6
test(cli/publish): sync — new CatalogTarget-based publish flow
fas89 Aug 29, 2026
1eb43dc
feat(forge): sync forge_copilot_seed.py — SeedOptions + prompt/diff h…
fas89 Aug 29, 2026
eab8778
feat(forge): sync forge_copilot_prompts.py — seed_ground_truth in pro…
fas89 Aug 29, 2026
f38e64f
feat(forge): sync forge.py — seed_options plumbing, --seed-from help …
fas89 Aug 29, 2026
a94ae4a
feat(forge): sync forge_copilot_agent.py — seed_options in agent base
fas89 Aug 29, 2026
c067338
feat(forge): sync forge_modes.py — SeedOptions.from_args at CLI boundary
fas89 Aug 29, 2026
18f9e32
feat(forge): sync forge_copilot_runtime.py — seed load + ground-truth…
fas89 Aug 29, 2026
c6c14e1
test(forge): sync mocked-LLM tests for Phase 7 seed → guard → repair …
fas89 Aug 29, 2026
2495a46
feat(bitol): ODCS v3.2.0 + ODPS v1.1.0, version-selected validation, …
claude Sep 11, 2026
01b4ed6
fix(forge): seed ground truth excludes round-trip stash; real-LLM har…
claude Sep 11, 2026
7120c68
build(odcs): declare deepdiff; note vowl 0.0.6 supports ODCS v3.2.0
claude Oct 12, 2026
d798855
chore: sync build-manifest base_version to 0.7.8
claude Oct 12, 2026
72d4759
style: apply black 24.10.0 and ruff isort to branch files
claude Oct 12, 2026
7a18e62
feat(dmm): default `fluid dmm publish` to Bitol ODPS
claude Oct 12, 2026
fa13e55
test(dmm): add opt-in live Entropy CE end-to-end runbook
claude Oct 12, 2026
5e1ab72
fix(odcs): make mapper exports valid, version-aware and edit-aware
claude Oct 12, 2026
c35163e
fix(odps): lossless per-port bundle round-trip, contractId linking, s…
claude Oct 12, 2026
2eb0642
fix(opds-cli): honour version chain, raw stdout, full --out bundles, …
claude Oct 12, 2026
de95acb
fix(catalogs,dmm): restore publish back-compat and fix DMM spec opt-outs
claude Oct 12, 2026
847c87d
docs(dmm): document the ODPS default and the legacy DPS opt-out
claude Oct 12, 2026
8b54f89
fix(forge): make --seed-from ground truth FLUID 0.7.2-valid and robust
claude Oct 12, 2026
ca20a20
build: gate odcs-strict 3.10+ deps, add requests to core, test vowl skip
claude Oct 12, 2026
c8f79ed
fix(bitol): reconcile the review-fix workstreams at their seams
claude Oct 12, 2026
ce089c6
docs(changelog): rewrite Unreleased relative to 0.7.8; keep ODPS prod…
claude Oct 12, 2026
385811f
test(catalogs): don't fail on unrelated DeprecationWarnings (Python 3…
claude Oct 12, 2026
69199ee
fix(dmm): ODPS team.name is the created team id; 'dps' token means 0.0.1
claude Oct 12, 2026
2c388b3
fix(catalogs): keep the 0.7.8 CatalogAsset API and catalog-path seman…
claude Oct 12, 2026
fadbc68
fix(odps): lossless per-port/ODCS-only round-trip, owner/expects edit…
claude Oct 12, 2026
9044a7d
test(odps): capture warnings on the provider logger, not via propagation
claude Oct 12, 2026
05f1c34
fix(odcs): round-2 mapper defects (SLA split, v3.2.0 downgrade, team,…
claude Oct 12, 2026
31cdc2a
fix(forge,opds,docs): round-2 seed/CLI/docs review fixes
claude Oct 12, 2026
478cf2c
test(odps): schema-less per-port ODCS re-exports without `schema`
claude Oct 12, 2026
883fa02
docs(changelog): fold round-2 review fixes into the Unreleased entries
claude Oct 12, 2026
ef33458
fix: close the round-2 convergence findings
claude Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,9 @@ package-lock.json.bak
# MyPy
.mypy_cache/
.dmypy.json
dmypy.json
dmypy.json

# Repo-root test-run artifacts (some ODCS CLI tests write the default
# output name to cwd as a side effect — they live in pytest's tmp_path
# but the executable path resolution may surface them at the repo root)
/mycontract-fluid.yaml
281 changes: 281 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,38 @@ Providers are the bridge between your declarative contract and your target execu
| 🌩️ **aws** | Amazon Web Services | S3, Glue, Athena, Redshift, MWAA, IAM. |
| ❄️ **snowflake** | Snowflake | Databases, schemas, streams, tasks, RBAC, sharing. |

> Export-only providers for open data standards: **odps**, **odcs**, **datamesh-manager**.
> Open-standard providers: **odcs** (Open Data Contract Standard v3.1.0 / v3.2.0, bidirectional), **odps_bitol** (Bitol Open Data Product Standard v1.0.0 / v1.1.0, bidirectional), **odps** (ODPI v4.1, export-only), **datamesh-manager**.

### Which ODPS? — disambiguation

| Standard | Provider | CLI | What it is |
|---|---|---|---|
| **ODCS v3.1.0 / v3.2.0** | `odcs` | `fluid odcs ...` | Per-dataset contract (schema/quality/SLA). Bidirectional, lossless. Imports validate against the version the document declares; exports default to v3.1.0 (`ODCS_API_VERSION=v3.2.0` to opt in). Bitol.io. |
| **Bitol ODPS v1.0.0 / v1.1.0** | `odps_bitol` | `fluid opds --spec bitol-1.0.0` (default) / `bitol-1.1.0` | Data product wrapper referencing ODCS by `contractId`. Bidirectional, lossless. Imports accept either version. |
| **ODPI v4.1** | `odps` | `fluid opds --spec odpi-4.1` | Open Data Product Initiative v4.1 (Linux Foundation). Export-only single JSON. |

```bash
# Export FLUID → Bitol bundle (1 ODPS doc + N sibling ODCS contracts):
# ./out/<productId>.odps.yaml + ./out/<productId>.<port>.odcs.yaml
fluid opds export contract.fluid.yaml --spec bitol-1.0.0 --out-dir ./out

# Validate the product doc (Bitol vs ODPI is auto-detected; YAML or JSON)
fluid opds validate ./out/<productId>.odps.yaml

# Import any of: ODPS file, directory bundle, or lone ODCS file.
# The result is a FLUID skeleton — complete fluidVersion/kind/id/name,
# expose kinds and bindings before `fluid validate` / `fluid plan`.
fluid opds import ./out/<productId>.odps.yaml -o product.fluid.yaml
fluid opds import ./out/ -o product.fluid.yaml
fluid opds import ./out/<productId>.<port>.odcs.yaml -o port.fluid.yaml

# Seed the forge copilot with an existing standards doc: its schema and qos are ground
# truth (per-field ODCS quality is not carried over); the LLM adds builds/execution
# and each expose's kind/binding/semantics
fluid forge --seed-from existing-contract.odcs.yaml --context '{"goal":"add daily refresh"}'
```

See [`docs/BITOL_INTEROP.md`](docs/BITOL_INTEROP.md) for the full architecture, `contractId` convention, resolver behaviour, and round-trip guarantees.

---

Expand Down
159 changes: 159 additions & 0 deletions docs/BITOL_INTEROP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Bitol Interoperability

FLUID plugs into the wider Bitol data ecosystem through two open standards.

## The three standards under one roof

| Standard | What it describes | Authority | FLUID provider | CLI selector |
|---|---|---|---|---|
| **ODCS v3.1.0 / v3.2.0** | Per-dataset contract (schema, quality, SLA, governance) | [bitol-io/open-data-contract-standard](https://github.com/bitol-io/open-data-contract-standard) | `odcs` | `fluid odcs ...` |
| **Bitol ODPS v1.0.0 / v1.1.0** | Data product wrapper referencing ODCS contracts via `contractId` | [bitol-io/open-data-product-standard](https://github.com/bitol-io/open-data-product-standard) | `odps_bitol` | `fluid opds --spec bitol-1.0.0` (default) / `bitol-1.1.0` |
| **ODPI v4.1** | Open Data Product Initiative v4.1 (Linux Foundation) — single JSON document | [Open-Data-Product-Initiative/v4.1](https://github.com/Open-Data-Product-Initiative/v4.1) | `odps` | `fluid opds --spec odpi-4.1` |

**Bitol ODPS** and **ODPI v4.1** are unrelated despite sharing the "ODPS" three-letter slug. Phase 5 of this work made the disambiguation explicit in every layer (provider names, CLI flag, docstrings, features.yaml).

## Canonical-model architecture

FLUID is the canonical hub. Every export goes FLUID → standard; every import goes standard → FLUID. The Bitol ODPS provider delegates per-port ODCS rendering to the ODCS provider — there's exactly one place that knows how to translate schema/quality/SLA across the boundary.

```
FLUID (canonical)
↓
┌───────────────┼───────────────┐
↓ ↓ ↓
OdcsProvider BitolOdpsProvider OdpsProvider
│ │ ↓ │
│ │ └── per-port ODCS
│ │ (via OdcsProvider)
↓ ↓ ↓
*.odcs.yaml 1 ODPS doc single
+ N *.odcs.yaml ODPI-v4.1 JSON
```

## Export pattern (Bitol ODPS)

One FLUID contract with N output ports always emits:

- 1 `<productId>.odps.yaml` — the Bitol ODPS product doc.
- N `<contractId>.odcs.yaml` siblings — one per output port, referenced by `contractId`.

`contractId` follows the convention `{productId}.{portName}` and matches the `id` of the corresponding ODCS contract exactly. This linking invariant is enforced inside the provider (`BitolOdpsProvider.render` raises `ProviderError` on mismatch) and asserted in tests.

```bash
fluid opds export contract.fluid.yaml --spec bitol-1.0.0 --out-dir ./out
# ./out/<productId>.odps.yaml
# ./out/<productId>.<port1>.odcs.yaml
# ./out/<productId>.<port2>.odcs.yaml

# --out FILE writes the product doc to FILE and the per-port ODCS contracts
# next to it; the format follows the suffix unless -f/--format is given.
# Use a different directory than --out-dir: a directory import accepts only
# one ODPS doc.
fluid opds export contract.fluid.yaml --out ./single/product.json
# ./single/product.json
# ./single/<productId>.<port1>.odcs.json
# ./single/<productId>.<port2>.odcs.json

# No --out / --out-dir: the product doc is printed to stdout as JSON.
fluid opds export contract.fluid.yaml > product.json
```

With `--spec` omitted the emitted `apiVersion` follows `ODPS_API_VERSION`, else the imported document's own `apiVersion` (re-exports of imports), else `v1.0.0`; an explicit `--spec bitol-1.0.0` / `bitol-1.1.0` pins it. Exports are validated against the vendored schemas; `--no-validate-strict` still validates but reports problems as warnings on stderr instead of failing.

`--out-dir` and `-f yaml` apply to the Bitol specs only; `--spec odpi-4.1` always writes a single JSON document to `--out` (or stdout) and rejects `--out-dir` and `-f yaml` with an error (`-f json` is accepted).

No inline-embedding mode. The fragments layout is the only export pattern — it matches Bitol convention and `datacontract-cli` expectations.

## Import — three entry points, one FLUID skeleton

All three converge on the same FLUID skeleton. The input documents are schema-validated, but the output is a **skeleton, not yet a schema-valid FLUID contract**: add `fluidVersion` / `kind` / `id` / `name`, each expose's `kind`, and binding `format` / `location` (and map the platform to a supported provider) before `fluid validate` / `plan` / `apply` will accept it. The `*_passthrough` keys it carries are what make re-export lossless.

```bash
# 1. Single ODPS file — resolver finds sibling ODCS by contractId.
fluid opds import out/<productId>.odps.yaml -o product.fluid.yaml

# 2. Directory — auto-discovers the ODPS doc + all ODCS files inside.
# Also handles ODCS-only directories (no ODPS doc → multi-expose FLUID
# with no product wrapper, warning emitted).
fluid opds import out/ -o product.fluid.yaml

# 3. Lone ODCS file — produces a single-expose FLUID.
fluid opds import out/<productId>.<port1>.odcs.yaml -o port1.fluid.yaml
```

With `-o` the format follows the file suffix (`.json` → JSON, anything else → YAML) unless `-f/--format` is given. Without `-o` the FLUID document is written to stdout (YAML, or JSON with `-f json`).

Flags:

- `--no-remote` — refuse http(s) `contractId` resolution (hermetic CI).
- `--lenient` — downgrade output-port resolution failures to warnings. Input ports are always lenient because their contracts often live in other repos / registries.

## Validate

```bash
fluid opds validate out/<productId>.odps.yaml # auto-detected as Bitol
fluid opds validate out/<productId>.odps.yaml --spec bitol-1.1.0 # pin the schema version

fluid opds export contract.fluid.yaml --spec odpi-4.1 --out odpi.json
fluid opds validate odpi.json --spec odpi-4.1 # ODPI v4.1 (also: --version 4.1)
```

YAML and JSON are both accepted. With `--spec` omitted, a document declaring `apiVersion` and `kind: DataProduct` is validated as Bitol ODPS against the vendored schema matching its own `apiVersion`; anything else is validated as ODPI v4.1, exactly as before. `fluid opds info --spec <spec>` shows the details of one specification.

## The `contractId` resolver

`fluid_build.providers.odps_standard.resolver.ContractResolver` resolves `contractId` references with this priority:

1. **Explicit hint** — when a caller passes a file path or URL alongside the id.
2. **Pre-indexed file** — files passed in via `additional_files=` or discovered by `index_directory()`. The directory import path uses this so local files always beat anything else.
3. **Local file probes** — across the cross-product of `{<base>, contracts/}` × `{<full-id>, <last-segment>}` × `{.odcs.yaml, .odcs.yml, .odcs.json, .yaml}` (16 candidates for a dotted id). Other layouts (an `odcs/` subdirectory, plain `.yml` / `.json` names) are found by directory import (`fluid opds import <dir>/`), which indexes every `.yaml` / `.yml` / `.json` file under the directory by its ODCS `id`.
4. **HTTP(S) URL** — when the `contractId` itself is a URL and `allow_remote=True`. Refuses HTML-with-200 responses and non-http(s) schemes.

Every resolved document is validated against the vendored ODCS JSON Schema matching its own `apiVersion` (v3.2.0 and v3.1.0 are vendored; older versions are checked against the newest, which is a strict superset); `ContractValidationError` raises with the offending field name.

## Round-trip guarantees

- **ODCS → FLUID → ODCS** is structurally lossless. Pass-through namespace (`metadata.odcs_passthrough.*`, `expose.odcs_passthrough.*`, `field.odcs_passthrough.*`) preserves every section the FLUID model doesn't natively represent.
- **FLUID → Bitol ODPS → FLUID → Bitol ODPS** reproduces the product doc and every per-port ODCS contract zero-diff (tested in `tests/test_odps_bitol_import.py::TestBundleRoundTrip`).
- `OdcsProvider.roundtrip_check(odcs)` is a public primitive returning a structured `{equal, missing, extra, changed}` diff — used by tests and the forge ground-truth guard.

## `fluid forge --seed-from` — standards as structural seed

The copilot accepts the same three input shapes as `opds import` as a **structural seed**:

```bash
# Lone ODCS — single-expose FLUID skeleton
fluid forge --seed-from existing-contract.odcs.yaml --context '{"goal":"add daily refresh"}'

# Single Bitol ODPS file — multi-expose skeleton with sibling ODCS resolved
fluid forge --seed-from product.odps.yaml

# Directory bundle — same as above; resolves sibling ODCS files by contractId
fluid forge --seed-from ./contracts/
```

Each expose's schema and qos from the seed are **ground truth** the LLM must not modify, and every seed expose must be kept, in order, under its `exposeId`. They are presented to the LLM as valid FLUID 0.7.2: an ODCS schema object or ODPS port name that is not a FLUID identifier is normalised deterministically (lower-cased, other characters replaced by `_`, so `Daily Orders` becomes `daily_orders`), and ODCS-only keys such as per-field `quality` rules are **not carried** into the FLUID output; add them to the generated contract yourself if you need them. The pre-processor (`fluid_build.cli.forge_copilot_seed.load_seed`) emits a `SeedResult` carrying:

- `fluid` — the imported skeleton the LLM augments.
- `provenance` — every contract source consumed (paths + URLs).
- `ground_truth_paths` — dotted paths inside `fluid` that must round-trip.
- `diff_against_seed(seed, candidate)` — primitive returning `{path, seed, candidate}` mismatches whenever the candidate mutated a ground-truth value (schema, qos or an `exposeId`).

The LLM fills in builds (with their execution) and each expose's kind, binding and semantics — the things the standards don't model. A directory seed reads only files inside the directory: symlinks that resolve outside it are ignored.

## File map

| Concern | Lives in |
|---|---|
| ODCS provider orchestrator | `fluid_build/providers/odcs/provider.py` |
| ODCS section mappers (1 per section) | `fluid_build/providers/odcs/mappers/*.py` |
| ODCS validation + round-trip diff | `fluid_build/providers/odcs/validation.py` |
| Bitol ODPS provider orchestrator | `fluid_build/providers/odps_standard/provider.py` |
| Bitol ODPS section mappers | `fluid_build/providers/odps_standard/mappers/*.py` |
| Bitol ODPS schemas | `fluid_build/providers/odps_standard/schemas/odps-product-v1.1.0.json`, `…-v1.0.0.json` |
| ODCS schemas | `fluid_build/providers/odcs/odcs-schema-v3.2.0.json`, `…-v3.1.0.json` |
| Upstream example fixtures (gold standard) | `tests/fixtures/upstream/{odcs,odps}/` — verbatim Bitol examples, exercised by `tests/test_spec_version_matrix.py` |
| `contractId` resolver | `fluid_build/providers/odps_standard/resolver.py` |
| CLI dispatcher | `fluid_build/cli/opds.py` |
| Forge `--seed-from` pre-processor | `fluid_build/cli/forge_copilot_seed.py` |
| Tests | `tests/test_odcs_roundtrip.py`, `tests/test_odps_bitol_export.py`, `tests/test_odps_bitol_import.py`, `tests/test_odps_resolver.py`, `tests/test_opds_cli_spec.py`, `tests/test_forge_copilot_seed.py` |
73 changes: 56 additions & 17 deletions docs/DATA_MESH_MANAGER_PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ fluid datamesh-manager publish contract.fluid.yaml

Registers (or updates) the data product in Entropy Data. Output ports are
included in the registration but **no data contracts are created yet** — the
ports show a `dataContractId` field so they're ready to be linked once contracts
are published.
ports carry a `contractId` field (`dataContractId` in the
[legacy DPS shape](#legacy-data-product-specification-001)) so they're ready to
be linked once contracts are published.

---

Expand All @@ -76,9 +77,9 @@ fluid datamesh-manager publish contract.fluid.yaml --with-contract
This is the full publish. It:

1. Registers the data product via `PUT /api/dataproducts/{id}`
2. For every expose port, generates an ODCS v3.1.0 contract and sends it via
2. For every expose port, generates an ODCS contract (v3.1.0 by default; set `ODCS_API_VERSION=v3.2.0` to emit v3.2.0) and sends it via
`PUT /api/datacontracts/{productId}.{exposeId}`
3. Each output port's `dataContractId` field in the product record already
3. Each output port's `contractId` field in the product record already
points to its contract — Entropy Data automatically links them.

---
Expand Down Expand Up @@ -158,7 +159,7 @@ fluid dmm publish contract.fluid.yaml --dry-run

### ODCS contract (per expose port)

Each expose in your FLUID contract becomes one ODCS v3.1.0 document. The
Each expose in your FLUID contract becomes one ODCS document (v3.1.0 by default, v3.2.0 on request). The
mapping is straightforward:

| FLUID field | ODCS field | Notes |
Expand All @@ -179,25 +180,63 @@ independently at its own URL:
https://api.entropy-data.com/api/datacontracts/{productId}.{exposeId}
```

### ODPS data product record
### ODPS data product record (default)

The top-level FLUID contract maps to Entropy Data's ODPS-aligned data product
shape:
By default the top-level FLUID contract is sent as a **Bitol Open Data Product
Standard (ODPS)** document — the only data product type current Entropy Data /
Entropy CE organisations accept:

| FLUID field | ODPS / Entropy Data field |
|---|---|
| `id` | `info.id` |
| `name` | `info.name` |
| `description` | `info.description` |
| `domain` | `info.domain` |
| `metadata.owner` | `teamId` |
| `exposes[]` | `outputPorts[]` (each with `dataContractId`) |
| — | `apiVersion: v1.0.0`, `kind: DataProduct` |
| `id` | `id` |
| `name` | `name` |
| `description` | `description.purpose` |
| `domain` | `domain` |
| `metadata.owner` | `team` (`name` + `members[]`); `--team-id` overrides `team.name` |
| `exposes[]` | `outputPorts[]` (each with `contractId`) |
| `expects[]` | `inputPorts[]` |

The `dataContractId` on every output port follows the pattern
The `contractId` on every output port follows the pattern
`{productId}.{exposeId}`, which is exactly the URL path used when the ODCS
contracts are PUT — so the link is automatic.

### Legacy Data Product Specification (0.0.1)

Older Entropy Data / Data Mesh Manager organisations that still expect the
legacy **Data Product Specification 0.0.1** shape can opt out of ODPS. The
legacy payload uses `dataProductSpecification: "0.0.1"`, a root `id`,
`info.title` / `info.owner`, `teamId`, and `outputPorts[].dataContractId`
(same `{productId}.{exposeId}` pattern).

Opt out with any of:

```bash
# per command
fluid dmm publish contract.fluid.yaml --data-product-spec 0.0.1

# for every publish in this shell / CI job (both fluid dmm publish and
# fluid publish --catalog datamesh-manager)
export DMM_DATA_PRODUCT_SPEC=0.0.1
```

or, for `fluid publish --catalog datamesh-manager`, in the catalog config:

```yaml
catalogs:
datamesh-manager:
endpoint: https://api.entropy-data.com
data_product_spec: "0.0.1" # or: provider_hint: dps
```

The specification is resolved in this order (first match wins):

1. explicit `--data-product-spec` (or the catalog's `data_product_spec`)
2. provider hint: `odps` / `opds` → ODPS, `dps` → DPS 0.0.1
(catalog `provider_hint`)
3. the `DMM_DATA_PRODUCT_SPEC` environment variable
4. default: ODPS

---

## Why One Contract Per Expose Port?
Expand All @@ -208,7 +247,7 @@ different schemas, different SLAs, and potentially different lifecycle states.
Bundling them all into one contract would mean consumers can't selectively
subscribe to or enforce contracts on individual ports.

ODCS v3.1.0 is designed as a per-dataset contract. Generating one document per
ODCS is designed as a per-dataset contract. Generating one document per
expose port means:

- Consumers can reference exactly the port they depend on
Expand Down Expand Up @@ -263,5 +302,5 @@ fluid dmm publish contract.fluid.yaml --overlay overlays/$ENV.yaml \

After step 3, Entropy Data shows:
- One data product entry with two output ports
- Two ODCS contracts, each linked to its port via `dataContractId`
- Two ODCS contracts, each linked to its port via `contractId`
- The correct staging or production BigQuery locations in each `servers[]` block
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ <h4>Drift Detection</h4>
<div class="feature-card">
<div class="icon">📦</div>
<h4>Open Standards Export</h4>
<p>Export to ODCS v3.1 (Bitol.io) and ODPS v4.1 (Linux Foundation) with bidirectional import. Publish to Data Mesh Manager catalogs.</p>
<p>Lossless, bidirectional ODCS v3.1/v3.2 and Bitol ODPS v1.0/v1.1 (Bitol.io), plus ODPI v4.1 export (Linux Foundation). Publish to Data Mesh Manager catalogs.</p>
</div>
<div class="feature-card">
<div class="icon">🏗️</div>
Expand Down
2 changes: 1 addition & 1 deletion fluid_build/build-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ metadata:
# PyPI Publishing Strategy
pypi:
repository: "fluid-mono-pypi"
base_version: "0.7.7"
base_version: "0.7.8"

# Version naming convention per build type
# alpha: 0.7.1a{build_number} (e.g., 0.7.1a42)
Expand Down
Loading
Loading