Skip to content

Cross-environment Master Data synchronization (same tenant) - #10753

Open
dcenic wants to merge 66 commits into
mainfrom
features/647660MDMCrossEnv
Open

Cross-environment Master Data synchronization (same tenant)#10753
dcenic wants to merge 66 commits into
mainfrom
features/647660MDMCrossEnv

Conversation

@dcenic

@dcenic dcenic commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What & why

Summary
Extends Master Data Management from same-environment, company-to-company sync to cross-environment sync within the same tenant. A subsidiary environment pulls master data from a source environment over an authenticated ODataV4 endpoint, reusing the existing synchronization engine end-to-end.

The whole feature is gated behind the new Source Environment Name setup field: empty = existing same-environment behavior, unchanged; set = cross-environment path. Same-env code paths are behavior-neutral.

What's included

Source-side generic API (published as an ODataV4 web service)

  • GetCapabilities — wire-version/feature negotiation so a newer subsidiary only calls actions an older source implements.
  • GetRecords — paged change feed with a composite (SystemModifiedAt, SystemId) cursor, plus drain/scan fallbacks for tables without the composite key; requests a per-mapping field projection.
  • LastModifiedAtPerTable — cheap change-detection probe (index-tip FindLast, no summary table), reads uncommitted.
    Read-only, runs under the caller's permission set (no elevation); composite change-feed keys added selectively via tableextensions.

Subsidiary consumer

  • IMDM Data Source interface with Local and Cross-Env implementations; GetDataSource() routes by Source Environment Name.
  • Materializes wire responses into temporary records so the existing synch engine processes them unchanged.
  • OAuth2 client-credentials HTTP transport (same-tenant by construction — token endpoint derived from the local tenant), in-session token cache, 429/Retry-After backoff.
  • Capability negotiation gate and bounded, resumable streaming (per-mapping Source Change Cursor + MaxPagesPerRun) so a large initial load drains across several job runs.
  • Polling change detector (recurring job) that reschedules per-table sync jobs based on LastModifiedAtPerTable.
    Setup & enable flow
  • Connection Details wizard (URL / source company / OAuth client id + secret), modeled on Intercompany; secret stored in Isolated Storage.

Inline Media & Blob synchronization

  • Media and Blob fields sync inline (base64 in the change feed) within the framework's single tracked write, so there's no extra SystemModifiedAt bump and no false "modified in subsidiary" conflict.
  • 512 KB per-field cap (sized to stay under the platform's single-stream-read limit); over-cap fields are skipped with telemetry (no error → no retry loop), and a per-page inline-byte budget keeps media-heavy pages bounded.

Security

  • Security-audit logging for token acquisition and 401/403; operational telemetry on transport failures.
  • Dedicated read-only cross-environment permission set for the source app registration; de-wildcarded the objects permission set.

Privacy

  • Mandatory consent for privacy notice both on the source environment side (because we read data from it) and on the subsidiary environment side (because table filter with customer content is sent across to facilitate the filtered read).

Out of scope / deferred to vNext

  • MediaSet (Item Picture) and cross-tenant sync.
  • Media larger than 512 KB (this will need chunked transfer).
  • Source-side deletes are not propagated (deliberate, to avoid accidental data loss) and source-side media/blob clears aren't propagated cross-env — both documented limitations.

Linked work

Fixes AB#647660

How I validated this

  • I read the full diff and it contains only changes I intended.
  • I built the affected app(s) locally with no new analyzer warnings.
  • I ran the change in Business Central and confirmed it behaves as expected.
  • I added or updated tests for the new behavior, or explained below why none are needed.

What I tested and the outcome (required — be specific: scenarios, commands, screenshots for UI changes)

  • Unit/integration tests via an in-process transport (source + subsidiary in one environment): capability negotiation, paged/resumable reads, error surfacing (table/field unavailable, unindexed), cross-env enable flow, inline Media/Blob round-trips, over-cap skip, and the page byte budget.
  • Manual end-to-end validation on a SaaS sandbox (real OAuth/HTTP transport + read-only app-scope check) is tracked separately.

Risk & compatibility

Same-environment MDM is untouched (subscriber table, event-driven rescheduling, and existing media handling all preserved). All new behavior branches on Source Environment Name.

@dcenic
dcenic requested a review from a team August 28, 2026 07:31
@dcenic
dcenic requested review from a team as code owners August 28, 2026 07:31
@github-actions github-actions Bot added the AL: Apps (W1) Add-on apps for W1 label Aug 28, 2026
@dcenic dcenic added the Team: Finance GitHub request for Finance area label Aug 28, 2026
@github-actions github-actions Bot added Team: Integrations GitHub request for Integrations area and removed Team: Finance GitHub request for Finance area labels Aug 28, 2026
@github-actions github-actions Bot added this to the Version 30.0 milestone Aug 28, 2026
@github-actions

This comment was marked as resolved.

@github-actions

This comment was marked as off-topic.

@alexei-dobriansky

Copy link
Copy Markdown
Contributor

Good Sense Reviewer - Round 9

Recommendation: Request Changes

What this PR does

This round adds source-side contact business relation reads for cross-environment contact linking, repairs source API web-service self-healing, and removes tenant values from telemetry messages. The prior contact-link and web-service gaps are addressed. One new mapping-filter change is not correct for cross-environment: it assumes targeted SystemId reads already honor the mapping filter, but that path fetches by SystemId with an empty row filter.

Status of previous suggestions
ID Title Status Author response
S1 Detector re-acquires token and capabilities every run Addressed
S2 Keyless drain page can send too many bytes at once Addressed
S3 Test Connection does not handle an unreachable source cleanly Addressed
S4 Materialized rows lose source modified time Addressed
S5 Read source contact relations before linking Addressed
S6 Do not skip updating existing web service Addressed
New observations (commits since round 8)

S7 (🔴 High): Apply mapping filters cross-environment too
Do not skip the integration-table filter when the setup is cross-environment. The SystemId lookup fetches from the source with an empty row filter, so this can match a record that the mapping filter excludes and then use the wrong mapping. Re-check the filter after the lookup, or pass the mapping filter into the targeted source read.

Risk assessment and necessity

Risk: The latest contact-linking and web-service changes reduce the earlier risk, and the source event timing was verified against the BaseApp publishers. The remaining risk is data integrity in filtered cross-environment mappings: a record outside a mapping filter can still be treated as belonging to that mapping, which can drive the wrong synchronization behavior.

Necessity: The same-tenant cross-environment feature is still needed and the scope is aligned with that goal. The mapping-filter regression must be fixed before merge because it can persist incorrect master-data synchronization results.


[AI-PR-REVIEW] version=1 promptVersion=4 system=github pr=10753 round=9 by=alexei-dobriansky at=2026-09-09T22:22:45Z lastSha=df50a4a30c5b75ebba38f9498b7727e07e811a67 reviewKey=d978e8f6ebcd6e036f47105b6acfaf5dc105e3cd67c6fc1cc3688e20cfac2479 suggestions=S1@6d7adfab:addressed,S2@6de0a4ce:addressed,S3@24600b24:addressed,S4@709ac168:addressed,S5@a40d1f06:addressed,S6@3b8ac2ed:addressed,S7@0fbe17fe:new parentRound=8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AL: Apps (W1) Add-on apps for W1 Team: Integrations GitHub request for Integrations area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants