Implementation review: PB-082, PR 701
| Field | Value |
|---|---|
| Backlog item | PB-082 Versioned data contracts with consumer declarations (feature FEAT-MESH-05), owned by the Staged roadmap as FD-127. The criteria are "As defined in FD-127 by its owning program". FD-127 maps to stage 8.2 Versioned schema contracts in docs/scale-and-operations-plan.md:186 (FD-127 → "Consumer declarations, deprecation (stage 8.2)", docs/product/enterprise-gap-analysis.md:87; owner "Staged roadmap", enterprise-backlog.md:56). |
| Pull request | #701, merged as squash commit 134e708d (parent 786291de). Quality log: PASS 9.1, then re-check PASS 9.2 (docs/quality/pr-review.md:277-278). |
| Package | none |
| Reviewer | Engineering quality review thread. It didn't write #701. |
| Date | 2026-10-27 |
| Environment | Linux container, main 6159e507, Python venv for the engine, Playwright Chromium. No tenant, no Windows, no second PC. |
Outcome#
Changes Required.
The engine does what 8.2 asks, and the tests prove it. Ports keep a version history. Links declare accepts. A deprecation date sends one Teams notice and is enforced after it passes. Two problems stop acceptance:
- Security (finding 1). A security gap in publishing data products (BQ-013) is withheld here until its fix merges; the security thread has the details.
- Widening
acceptscan't reach the catalog (finding 2). When a consumer'sacceptsblocks the producer, the block message tells the consumer to "widen accepts in the link". Editing the link file doesn't reach the catalog, so the producer stays blocked. The app has no screen to change an existing link. Adding the link again with the same alias from New link clearsacceptsin the file and in the catalog, so the link then accepts any version.
All of 8.2 is engine-only. There is no screen for accepts, link columns or port history, and product_history has no engine RPC (finding 4). The deprecation date was already editable in the product dialog.
Recommended by: Engineering quality review. Decided by (product owner): pending.
Acceptance criteria#
The criteria come from FD-127 / 8.2 (docs/scale-and-operations-plan.md:186): "Ports carry a schema version and history in the catalog; consumers declare a compatible range; deprecation sets a date, notifies consumers through the 9.1 alert path and is enforced after it."
| # | Criterion | Result | Evidence |
|---|---|---|---|
| 1 | Ports carry a schema version and history in the catalog | Met (engine) | New table catalog.port_version (central/core.py _CATALOG). register_product adds one row per port per environment when the version or columns change (_record_port_version, core.py:759). Re-publishing the same thing adds no row. product_history (core.py:773) returns rows newest first, and is exposed as a UDF (central/udf.py). Ids end in a random part, so two publishes in the same microsecond keep both rows. Tests: test_history_gets_a_row_only_when_the_version_or_columns_change, test_two_publishes_in_the_same_microsecond_keep_both_history_rows. Nothing in the app reads the history (finding 4). |
| 2 | Consumers declare a compatible range | Met in the engine; widening is broken (finding 2) | accepts = "2" / "2-3" / "2+" on a link (meta/mesh.py:42-67, the validate save gate). The catalog keeps it, with the link's [columns], in two new catalog.link columns (register_link, core.py:1069). A new link saved without accepts gets the product's current version (central/service.py:1165, _current_version). Older links keep reading any version. breaking_changes (mesh.py:428) blocks a raised version that a reader's accepts leaves out, and a reader that listed its columns is only broken by a change to one of them. The consumer can widen accepts only by calling mesh_save_link again with it. The UI's New link never sends it (app/ui/mesh.js:410), and editing the file isn't synced (probe below). |
| 3 | Deprecation sets a date, notifies consumers (8.2 says through the 9.1 alert path) and is enforced after it | Met, with a different notice path | The date comes from the product's deprecated_on (already in the product dialog, app/ui/mesh.js:312). The first publish as deprecated, or one with a new date, sends one product.deprecated event per date (governance_notices.py:125, deduplicated on product.deprecated:<id>:<date>). It goes to the platform's Teams routes (workloads/platform_notify.py), not the 9.1 incident path. 9.1 moved to another program (scale-and-operations-plan.md:148), and the plan's own status line says "a Teams notice". A notice that fails never fails the publish. Enforcement: after the date, register_link refuses new links (core.py:1088), and _deprecation_gate (meta_service.py:783, run in both promote check and promote) blocks the consumer's promote. Runs aren't stopped, by design. Tests: test_a_new_link_is_refused_once_the_deprecation_date_comes, test_a_link_to_a_product_past_its_deprecation_date_blocks_the_consumers_promote, test_deprecating_a_product_tells_its_readers_once_and_a_failed_notice_doesnt_fail_the_publish, test_a_prod_deprecation_is_told_once_not_again_as_a_product_change. Anyone can set the date (finding 1). |
Probes (my own, against main 6159e507, LocalPlatform SQLite)#
- Widening and re-saving a link. The consumer saves a new link to
sales/orders(version 3). The file and the catalog both haveaccepts = "3". I edited the file to"3-4"and reloaded: the file says3-4, the catalog still says3. I then saved the same alias again the way the New link dialog sends it, with noaccepts. The file and the catalog both became''(any version). - Withheld until BQ-013's fix merges (see finding 1).
Checks#
| Check | Result | Evidence or note |
|---|---|---|
| Scope matches the item and package | Met (engine) | It covers 8.2's engine part. scale-and-operations-plan.md:18,188 says "the view waits for a mockup". |
| New tests fail without the change, pass with it | Met | I extracted a tree at 134e708d and swapped in the pre-#701 versions of the 8 changed sidecar/fabric_desktop/ files (git show 134e708d^:...). test_versioned_contracts.py gave 17 failed, 1 passed. The test that still passes, test_only_an_unknown_argument_is_retried_without_accepts, is a guard: it checks a catalog refusal isn't retried, which also held before. On main the file gives 18 passed. |
| Full suites pass (pytest, UI, cargo) | See below | On main 6159e507: test_versioned_contracts.py gave 18 passed. test_breaking_changes.py, test_governance_notices.py, test_platform_notify.py, test_mesh_home.py and test_tsql_contract.py gave 355 passed, 70 skipped. All 70 skips are the live T-SQL contract, which needs FABRIC_DESKTOP_TSQL_LIVE=1. test_platform.py and test_meta.py gave 86 passed. node --test app/tests/ui.test.cjs: 297 pass, 0 fail. Browser mesh-project.test.cjs plus best-practice-checks.test.cjs: 10 pass, 0 fail. Full pytest sidecar/tests on a copy of main, 4982 passed, 77 skipped, 1 failed in 22 min. The failure, test_agent.py::test_agent_runs_a_queued_environment_with_run_details (agent run table count), is unrelated to this PR and passed on rerun (test_agent.py 38 passed): a timing flake under load. cargo: not relevant. |
| Reachable where docs say; Preview behavior right; packaged into the engine | Engine-only | The new behaviour runs from promote, publish and mesh_save_link. No UI sets accepts or link columns, or shows history, and no engine RPC calls product_history. docs/data-mesh.md describes accepts as a field in the link file and product_history as a platform operation, which is accurate. |
| OneLake data safety | Met | Nothing touches OneLake. Catalog changes are additive: ensure_contracts (core.py:1114) creates port_version and adds two nullable link columns, and is safe to run again. test_a_platform_made_before_versioned_contracts_still_works covers an older catalog. |
| Authentication and distribution | Not met | No new scope. Finding 1 (withheld until BQ-013's fix merges). product_history and list_products take no caller context, so anyone can read any product's port history and columns, which matches what list_products already exposes (finding 5). The UDF signature changed (register_link gained accepts, columns; product_history is new), so UDF_Platform needs a redeploy. The engine handles an older function: it retries without the new arguments only when the refusal names them, and the answer says to run Finish or repair (service.py:41, _register_link). |
| No unproven local-equals-Fabric claim; Preview tags kept | Partly | The DDL in ensure_contracts (ALTER TABLE ... ADD, T-SQL vs SQLite) and the UDF default arguments (accepts: str = "", columns: dict = {}) have not run against Fabric SQL database or the UDF runtime. Unable to verify here (no tenant). The Data products and Links sections of docs/data-mesh.md don't carry a Preview tag or a "not yet tried against a live tenant" line for these (finding 6). |
| Docs match the code; linked; strict docs build passes | One mismatch | docs/data-mesh.md:154-225 and governance-guide.md match the engine. The exception is "Its project can widen accepts in the link", which doesn't work by editing the link (finding 2). zensical build not run (not installed here). |
| Accessibility of new controls | Not applicable | No new controls. |
Inventory and backlog updated; product_data.py all consistent |
Not met | FEAT-MESH-05 still lists only #71, and its next action is still "Consumer declarations and deprecation (scale plan 8.2)". PB-082 is Proposed with no prs (finding 3). product_data.py all on a copy of main: "Product data is consistent." |
Findings#
| # | Severity | Finding | Where | Status |
|---|---|---|---|---|
| 1 | blocking | A security gap in publishing data products (BQ-013) is withheld here until its fix merges; the security thread has the details. | central/core.py |
Open: BQ-013 |
| 2 | should fix | A consumer can't widen accepts where the block tells it to. The producer's block says "Its project can widen accepts in the link (for example accepts = "1-2")". Editing links/<alias>.toml never reaches catalog.link: only mesh_save_link calls register_link, and no promote or publish syncs links. The UI only has New link, which doesn't send accepts or columns. Re-adding the same alias there clears accepts in both the file and the catalog, so the link accepts any version again (probe 1). Fix: re-register a project's links (with accepts and columns from the files) on promote or on a link save from the file, and keep the file's accepts when the dialog doesn't send one. |
central/service.py:1152-1180; app/ui/mesh.js:410; meta/mesh.py:502 (message) |
Open |
| 3 | should fix | The inventory and backlog don't record #701. FEAT-MESH-05 has only #71, and its next action is the work this PR did. PB-082 is Proposed with no prs. |
docs/product/FEATURE_INVENTORY.json FEAT-MESH-05; PRODUCT_BACKLOG.json PB-082 |
Open (with the outcome) |
| 4 | note | Engine-only. There is no UI for accepts, link columns or port history. product_history exists only as a catalog/UDF operation, with no engine RPC. A consumer who wants to list its columns has to write [columns] by hand, and per finding 2 that doesn't reach the catalog either. The plan says the view waits for a mockup. |
scale-and-operations-plan.md:188 |
Documented in the plan |
| 5 | note | product_history takes no caller context and returns columns for private products as well. The same is already true of list_products (ports, columns, and consumers with updated_by, i.e. who linked). That also exposes part of what #763 treats as personal follower data. This is not new in #701, but worth a look with finding 1. |
central/udf.py product_history, list_products; core.py:1035-1047 |
Note |
| 6 | note | 8.2's catalog DDL and the changed UDF signatures haven't been tried in Fabric. The docs neither say so nor mark the new parts Preview. | docs/data-mesh.md:154-225 |
Open |
| 7 | note | Any change of version counts as "raised", including a lower number. A product moved from 2 back to 1 with a dropped column gets the "new version" handling. Tests use dt.date.today() (local time) while the code uses UTC days, so a test run near midnight UTC could fail. |
meta/mesh.py:454; tests/test_versioned_contracts.py:15 |
Note |
Inventory and backlog changes#
- PB-082:
state→ In review (Changes Required);prs701; this record added toevidence. - FEAT-MESH-05: add #701,
sidecar/tests/test_versioned_contracts.py, andcentral/core.py/central/service.pyas source; reach "engine only (promote, publish, link save); no view"; next action: findings 1 and 2, then 8.2's view after the mockup; this record added toevidence.
Need a hand?
Find a fix in troubleshooting, or share a reproducible issue with the community.
Get support ↗Improve this guide on GitHub ↗