Skip to content

Optimize mobile Dataverse metadata execution - #421

Open
Shubham Agarwal (kanu-shubham) wants to merge 22 commits into
mainfrom
fix/mobile-dataverse-fast-create-v2
Open

Optimize mobile Dataverse metadata execution#421
Shubham Agarwal (kanu-shubham) wants to merge 22 commits into
mainfrom
fix/mobile-dataverse-fast-create-v2

Conversation

@kanu-shubham

@kanu-shubham Shubham Agarwal (kanu-shubham) commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR supersedes #313 and contains both the original Dataverse fast-create work and the complete V2 optimization. PR #313 does not need to merge first.

The change makes mobile-app Dataverse schema creation faster, safer, tenant-correct, and efficiently rerunnable while preserving strict sequential metadata writes and equivalent resulting schemas.

Controlled live benchmarks show:

  • 59.6%–69.9% faster first-run creation across 6, 12, and 24 new-table workloads
  • approximately 2.5x faster for medium and large schemas
  • more than 97% faster zero-write reruns
  • 0 normalized schema differences across every paired run
  • 0 metadata-lock errors

Problems addressed

The previous workflow incurred substantial avoidable overhead around Dataverse's inherently sequential metadata engine:

  • every metadata operation launched another Node process
  • token resolution repeatedly rediscovered the Azure account and tenant
  • hundreds of requests encountered avoidable authentication challenges
  • tables, columns, keys, and relationships were repeatedly probed one at a time
  • reruns spent minutes rediscovering components that were already present
  • unreadable metadata could be mistaken for an absent component
  • incomplete create attempts were difficult to resume safely
  • data-model planning could overextend loosely related tables or create unnecessary projections and relationships

Dataverse still serializes metadata writes, so this PR does not attempt unsafe parallelism. It removes client-side process, authentication, discovery, and reconciliation overhead around the required sequential requests.

What changed

1. Long-lived sequential metadata executor

plugins/mobile-apps/scripts/dataverse-request.js

Adds BATCH-METADATA, a long-lived executor that:

  • acquires and reuses authentication state
  • executes metadata operations strictly in declared order
  • preserves tier and relationship dependencies
  • records operation-level timing and status
  • retains existing retry and backoff behavior
  • stops on the first unsafe failure instead of continuing with a partially invalid dependency graph
  • does not convert throttling followed by a collision into false success

BATCH-METADATA is not an OData $batch. It sends ordinary Dataverse HTTP requests one at a time from one Node process.

2. Explicit tenant propagation

Updated:

  • scripts/lib/validation-helpers.js
  • scripts/dataverse-request.js
  • scripts/detect-publisher-prefix.js

The target environment's explicit tenant now takes priority during token acquisition and propagates through metadata helpers.

Tenant resolution order is deterministic:

  1. explicit tenant argument
  2. POWER_PLATFORM_TENANT_ID
  3. DATAVERSE_TENANT_ID
  4. Dataverse authentication challenge
  5. Azure account discovery
  6. final unqualified-token fallback

This avoids repeated az account show calls and prevents tokens from being requested against the wrong tenant when Power Apps CLI and Azure CLI identities differ.

3. Snapshot-driven metadata reconciliation

The workflow now prefers filtered metadata snapshots over repeated per-component discovery.

The desired schema is compared locally against live metadata before writes begin. The snapshot folds in preflight information for:

  • existing tables
  • columns and types
  • lookup relationships
  • many-to-many relationships
  • alternate keys
  • targeted derived metadata for lookups, choices, Boolean columns, and externally created computed columns

This reduces requests and allows zero-write reruns to finish quickly.

4. Safer idempotent reruns

The create flow now distinguishes:

  • known components created by this app
  • compatible foreign components that can be reused or extended
  • incompatible name collisions
  • unreadable metadata
  • partially completed prior runs
  • recent-delete tombstones and hidden collisions

Reruns:

  • skip matching existing components
  • create only missing columns or relationships
  • stop on incompatible type drift
  • avoid re-posting complete table definitions after a partial commit
  • preserve a logical-name alias map when collision recovery renames a table

Unreadable metadata is never silently interpreted as “not found.”

5. Collision recovery and reuse behavior

The add-dataverse workflow now prefers:

  1. reuse when the existing schema already satisfies the plan
  2. extend when the existing table represents the same business concept
  3. rename and create only for incompatible concepts, reserved names, or tombstones

For example, if a user selects an environment where an appropriate product or warehouse table already exists, the workflow reuses it or adds only missing compatible columns instead of blindly creating a duplicate.

When a rename is required, downstream relationships, screen references, plan artifacts, and run-level aliases must use the resolved logical name.

6. Faster new-table creation

All ordinary planned columns are included in the initial EntityDefinitions create request.

Instead of:

create table
→ create column 1
→ create column 2
→ create column 3

V2 uses:

create table with all ordinary columns inline

Individual column requests remain only for:

  • extending existing tables
  • recovering from a partial prior table creation
  • lookup relationships
  • lookup relationships and other dependency-ordered relationship metadata

7. Dependency-safe relationships and keys

The workflow keeps metadata writes sequential and tier ordered:

  • all parent tables exist before child relationships
  • both lookup endpoints exist before RelationshipDefinitions
  • many-to-many relationships receive snapshot preflight
  • alternate keys are created only after their columns exist
  • key activation status is captured as Active, Pending, or Failed

No parallel metadata writes were introduced.

8. Solution targeting and mutation hygiene

Metadata mutations consistently target the intended Dataverse solution through MSCRM.SolutionUniqueName.

Project-local scratch files are used for request payloads, and the workflow records server-assigned metadata IDs so future runs can distinguish app-owned components from foreign collisions.

9. Derived metadata and cross-entity-read safety

The workflow now validates the complete semantics of same-named derived columns before reuse:

  • lookup target sets must match exactly
  • Choice and Boolean mappings require valid integer values and labels
  • ordinary columns require SourceType 0
  • externally created computed columns require matching source type, source-type mask, and exact FormulaDefinition
  • malformed, duplicate, unreadable, or invalid metadata blocks all writes in the affected run

Microsoft does not expose a supported API contract for authoring formula, calculated, or rollup definitions. The old calculated-column helper therefore fails closed before authentication or mutation. Cross-entity reads use formatted lookup annotations or bounded detail-screen fetches; hot-list fields and unsupported M:N paths remain external-projection-required instead of generating XAML or N+1 reads.

10. Reuse/extend/create cost guardrails

The architect now considers more than name similarity when deciding whether to reuse or extend an existing table.

The decision accounts for:

  • authoritative business ownership
  • semantic fit
  • number and proportion of extension columns
  • relationships
  • shared integrations
  • security and reporting consequences
  • offline requirements
  • metadata creation cost

This allows an app-owned table when extending a merely similar shared table would create excessive coupling and serialized metadata writes.

11. Relationship-count guidance

Relationship creation is the dominant remaining Dataverse cost. The planner now requires every relationship to have a real runtime, integrity, security, offline, or reporting purpose.

Standard Dataverse ownership and audit columns should be reused when they express the requirement. Business-specific relationships remain when they have distinct meaning.

12. Review follow-up and cross-platform CI

Review feedback is addressed in 94e3a20c, with final OS-independent test portability in 596602c7:

  • documented the explicitTenantId parameter in getAuthToken JSDoc
  • documented PUT as a supported request method
  • documented --tenant-id for single and BATCH-RECORDS calls
  • documented --continue-on-error for BATCH-METADATA
  • replaced shell-dependent fake Azure CLI scripts with an OS-independent Node preload that intercepts Azure CLI child-process calls
  • removed PATH and executable-resolution dependencies from the Azure CLI tests
  • added path-filtered mobile-app script CI across Ubuntu, Windows, and macOS with Node 20 and 22

Files changed

File Purpose
plugins/mobile-apps/agents/data-model-architect.md Reuse/extend/create, relationship, and projection planning guardrails
plugins/mobile-apps/scripts/create-calculated-column.js Fail-closed guard for unsupported formula-definition mutation
plugins/mobile-apps/scripts/lib/derived-metadata.js Deterministic derived-column compatibility checks
plugins/mobile-apps/scripts/validate-derived-metadata.js Pre-write expected/live metadata validation CLI
plugins/mobile-apps/scripts/tests/derived-metadata.test.js Derived-metadata and calculated-helper regression tests
plugins/mobile-apps/scripts/dataverse-request.js Long-lived sequential metadata executor and tenant support
plugins/mobile-apps/scripts/detect-publisher-prefix.js Tenant-safe publisher discovery
plugins/mobile-apps/scripts/lib/validation-helpers.js Deterministic tenant-aware token acquisition
plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js Token-resolution and cross-platform fake-CLI regression tests
plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js Metadata executor and cross-platform regression tests
plugins/mobile-apps/skills/add-dataverse/SKILL.md Complete optimized and hardened workflow guidance
.github/workflows/mobile-apps-script-tests.yml Path-filtered cross-platform script-test CI matrix

Benchmark methodology

The benchmark used frozen main and V2 revisions, isolated app shells, unique Dataverse logical names, identical desired schemas, captured HTTP telemetry, zero-write reruns, and normalized schema comparison.

The three workloads included:

  • reused and extended tables
  • ordinary columns
  • lookup relationships
  • pre-existing projection columns reconciled as dependencies
  • alternate keys
  • generated TypeScript services
  • publish and verification
  • schema generation
  • TypeScript checking

Benchmark results

New tables Relationships Metadata writes Services Main V2 Time saved Improvement Speed
6 28 113 15 20m 56.33s 6m 17.58s 14m 38.75s 69.9% 3.33x
12 54 151 21 29m 51.14s 11m 58.54s 17m 52.60s 59.9% 2.49x
24 94 219 33 46m 50.35s 18m 54.27s 27m 56.08s 59.6% 2.48x

Zero-write reruns

New tables Main rerun V2 rerun Improvement
6 12m 7.81s 18.81s 97.4%
12 17m 14.86s 24.62s 97.6%
24 26m 50.13s 34.24s 97.9%

Correctness and reliability

Across all three paired runs:

  • normalized schema differences: 0
  • metadata-lock errors: 0
  • HTTP 429 responses: 0
  • HTTP 5xx responses: 0
  • logical writes on V2 reruns: 0

Process and authentication reduction: 12-table workload

Metric Main V2
Command invocations 382 31
HTTP 401 responses 355 1
First-run duration 29m 51.14s 11m 58.54s
Zero-write rerun 17m 14.86s 24.62s

Why the remaining first-run time exists

For the 12-table V2 run, approximately 96% of the first-run duration was spent inside Dataverse metadata requests. Relationship creation alone represented approximately 64% of the full run.

The client-side executor is therefore close to the practical limit for creating the same schema through individual Dataverse metadata operations. Further substantial gains require reducing unnecessary schema operations or changing the metadata transport—not unsafe parallel writes.

Validation

After the conflict-free rebase and review fixes:

  • the complete mobile-app script suite passed
  • 40 tests passed
  • JavaScript syntax checks passed
  • all 6/12/24 live benchmark pairs completed
  • normalized schemas were equivalent for every pair
  • a path-filtered CI matrix now runs mobile-app script tests on Ubuntu, Windows, and macOS with Node 20 and 22

Compatibility and safety

  • metadata writes remain strictly sequential
  • BATCH-METADATA is not OData $batch
  • existing single-request behavior remains available
  • explicit tenant support is additive
  • retry and throttling behavior is preserved
  • failure-shaped responses are not converted into success
  • reruns do not perform unnecessary metadata writes
  • generated schemas remained equivalent in controlled comparisons
  • unsupported formula-definition writes now fail before authentication or mutation
  • required derived metadata is validated through targeted long-lived batch reads before any schema write

Scope boundaries

This PR is intentionally limited to Dataverse metadata execution and data-model planning guidance.

The following findings from the broader Pepsi audit will be handled separately:

  • sample-data partial-failure and exact-resume behavior
  • materialized-projection refresh ownership
  • server-enforced inventory posting and ledger integrity
  • agent capability preflight
  • consolidated create-mobile-app planning gates
  • deployment-readiness classification
  • end-to-end mutation journey evaluations

Benchmark visuals

6-new-table workload

6-table Dataverse V2 benchmark

12-new-table workload

12-table Dataverse V2 benchmark

24-new-table workload

24-table Dataverse V2 benchmark

Why fake-az-preload.js is included

plugins/mobile-apps/scripts/tests/helpers/fake-az-preload.js is an intentional test-only Azure CLI double. It lets authentication tests simulate tenant discovery, token retrieval, and tenant-specific failures without contacting Azure, depending on a developer’s active az session, or risking changes to real environments. It is loaded only by tests and is not used by production skill execution.

Copilot AI lite review requested due to automatic review settings August 14, 2026 04:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the mobile-apps plugin’s Dataverse metadata planning/execution flow by reducing repeated tenant discovery, introducing a strictly sequential metadata executor, and shifting the workflow toward snapshot-based reconciliation to improve performance and idempotency.

Changes:

  • Propagates an explicit tenant ID through Dataverse helper scripts to avoid repeated az/challenge-based tenant discovery.
  • Adds BATCH-METADATA to dataverse-request.js to run ordered metadata operations sequentially in one process, with per-op timing/results.
  • Updates /add-dataverse and the data-model architect guidance to use snapshot-driven reconciliation and clearer reuse/extend/create/adapt/defer decision rules; adds targeted tests for auth resolution and metadata batching.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
plugins/mobile-apps/skills/add-dataverse/SKILL.md Updates the skill workflow to reconcile from a batched metadata snapshot, preserve adapt/defer decisions, and prefer sequential execution (including the new batch executor).
plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js Adds tests for BATCH-METADATA ordering/stop-on-failure behavior and for avoiding “429 then collision => success” masking.
plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js Adds regression tests ensuring tenant resolution short-circuits expensive discovery and honors explicit tenant precedence.
plugins/mobile-apps/scripts/lib/validation-helpers.js Refactors getAuthToken to lazily produce tenant candidates and accept an explicit tenant argument.
plugins/mobile-apps/scripts/detect-publisher-prefix.js Adds --tenant-id support so publisher-prefix detection can reuse the resolved tenant.
plugins/mobile-apps/scripts/dataverse-request.js Adds --tenant-id, introduces BATCH-METADATA, and threads tenant through token refresh paths.
plugins/mobile-apps/scripts/create-calculated-column.js Adds --tenant-id support for calculated-column creation so it can reuse resolved tenant context.
plugins/mobile-apps/agents/data-model-architect.md Strengthens “target-grounded” planning guidance and expands decision taxonomy (reuse/extend/create/adapt/defer/unverified/block).
Suppressed comments (2)

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:103

  • The second test also uses a #!/bin/sh fake az, which makes the test shell-dependent. Using a Node-based fake keeps the test self-contained and consistent with auth-token-resolution.test.js.
    `#!/bin/sh
printf '%s\\n' '{"accessToken":"test-token"}'
`,
    { mode: 0o755 },

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:135

  • This PATH update also hard-codes ':' as the separator. Use path.delimiter so the fake az is reliably found on all OSes.
    { env: { ...process.env, PATH: `${tempDir}:${process.env.PATH}` } },
  );

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread plugins/mobile-apps/scripts/lib/validation-helpers.js
Comment thread plugins/mobile-apps/scripts/dataverse-request.js
Comment thread plugins/mobile-apps/scripts/dataverse-request.js
Comment thread plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js Outdated
Comment thread plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js Outdated
Document the tenant-aware CLI contract, make Azure CLI test shims cross-platform, and add path-filtered mobile-app script CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e31c561-a943-4464-92f3-04ab071393c0
Copilot AI review requested due to automatic review settings August 14, 2026 05:22
Normalize case-insensitive PATH keys before prepending fake Azure CLI shims so Windows resolves the test executable instead of the ambient CLI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e31c561-a943-4464-92f3-04ab071393c0

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (1)

plugins/mobile-apps/scripts/dataverse-request.js:34

  • The BATCH-METADATA header comment shows --tenant-id <id> as required, but the implementation accepts a null tenantId and will fall back to env vars / discovery (consistent with the single-request path and SKILL guidance). This doc mismatch can confuse callers into thinking BATCH-METADATA can’t run without an explicit tenant.
// BATCH-METADATA mode (strictly sequential metadata operations in one process):
//   node dataverse-request.js <envUrl> BATCH-METADATA <label> --operations '<json>' --tenant-id <id> [--continue-on-error]
//

Copilot AI review requested due to automatic review settings August 14, 2026 05:25
Intercept Azure CLI child-process calls through a Node preload so the auth and metadata tests run identically without shell or executable-resolution dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e31c561-a943-4464-92f3-04ab071393c0

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (4)

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:125

  • This fake az emits JSON, but production calls az account get-access-token --query accessToken -o tsv, which returns a plain token string. Keeping the shim output in the same shape as the real CLI makes the test more faithful and less likely to mask future token-handling regressions.
      'BATCH-METADATA',
      'collision',
      '--operations',
      JSON.stringify([{ method: 'POST', apiPath: 'EntityDefinitions', body: { value: 1 } }]),

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:50

  • The fake az used here prints JSON ({"accessToken":...}), but the production code invokes az account get-access-token --query accessToken -o tsv, which returns the raw token string. Returning JSON here makes the test less representative and could mask future token-parsing issues.

This issue also appears on line 122 of the same file.

      res.writeHead(204);
      res.end();
    };
    setTimeout(finish, 25);
  });

plugins/mobile-apps/scripts/dataverse-request.js:541

  • In BATCH-METADATA retry handling, Retry-After is parsed with parseInt(...) but the result isn’t validated. If the header is malformed (or an HTTP-date), delayMs becomes NaN and setTimeout(NaN) effectively retries immediately, which can hammer the service instead of backing off.
    if (res.statusCode === 429 && attempt < maxRetries) {
      const retryAfter = res.headers?.['retry-after'];
      const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 30000;
      rateLimited = true;
      await new Promise((resolve) => setTimeout(resolve, delayMs));

plugins/mobile-apps/skills/add-dataverse/SKILL.md:147

  • The invocation contract says <METHOD> is "GET / POST / PATCH / DELETE", but scripts/dataverse-request.js now explicitly supports PUT as well. Keeping this list in sync avoids confusion when the workflow (or future steps) needs a PUT metadata call.
  • Three positional args, in order: <envUrl>, <METHOD> (GET / POST / PATCH / DELETE), <apiPath> (everything after /api/data/v9.2/).
  • Body is a flag, not positional. --body '<json>' — required for POST/PATCH, never for GET/DELETE. Forgetting --body and passing the JSON as a 4th positional arg returns a usage error.
  • --include-headers adds response headers (needed for OData-EntityId after a record create).
  • Output is JSON: { "status": <code>, "data": <body> }. Token refresh on 401 and back-off on 429 are automatic — never wrap with manual retry.
</details>

Copilot AI review requested due to automatic review settings August 14, 2026 05:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (4)

plugins/mobile-apps/skills/add-dataverse/SKILL.md:223

  • Step 4’s reconciliation matrix introduces an unverified state that explicitly stops before writes, but the “Decide-before-write barrier” sentence immediately below says every item must resolve only to reuse/extend/create/adapt/defer. This is internally inconsistent and can mislead future edits of the workflow logic.
| Batched query failed (non-2xx) after retry and per-table split | `unverified` | unknown | STOP before writes for the affected reconciliation scope and surface the concrete environment/auth/permission error. |

`replace` is not an automatic state in this workflow. Replacing a table or column requires an explicitly approved migration with dependency analysis and data movement, so a conflict resolves to `adapt` (rename beside it) or `defer` (leave it out) instead — both of which leave existing data untouched.

**Decide-before-write barrier (HARD):** finish reconciliation for every table and column before the first metadata write. Every item must come out of Step 4 as `reuse`, `extend`, `create`, `adapt`, or `defer` — never as an unresolved conflict. Deciding renames up front is what keeps relationships, screens, and sample data pointing at the same names.

plugins/mobile-apps/agents/data-model-architect.md:34

  • The hard-rule classification list omits Block, but the workflow and Step 5 later instruct the agent to produce Block decisions. This inconsistency can cause the agent to output a decision the earlier rules say doesn’t exist (or vice versa).
- **No automatic replacement.** This agent classifies schema as `Reuse`, `Extend`, `Create`, `Adapt` (create beside a conflicting object under a new name), `Defer` (leave out of this run), or `Unverified` (target metadata could not be read). Replacing an existing table/column requires a separately approved migration with dependency analysis and data movement; it is outside this workflow. A data-modelling conflict is never a blocker — it is an `Adapt` or a `Defer` with a recorded reason.

plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js:59

  • This test run environment overwrites NODE_OPTIONS rather than appending, which can drop externally supplied Node flags (coverage/instrumentation) and make the tests behave differently depending on how they’re invoked. It’s safer to append the preload require to any existing NODE_OPTIONS.
    env: {
      ...process.env,
      NODE_OPTIONS: `--require=${FAKE_AZ_PRELOAD}`,
      FAKE_AZ_LOG: logPath,

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:26

  • The test helper overwrites NODE_OPTIONS, which can accidentally drop runner/user-provided Node flags (e.g., coverage/instrumentation) when these tests are executed in more constrained environments. Appending the preload to any existing NODE_OPTIONS is safer and still deterministic.
  return {
    ...process.env,
    NODE_OPTIONS: `--require=${fakeAzPreload}`,
    FAKE_AZ_STATIC_TOKEN: 'test-token',
    ...overrides,

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3e31c561-a943-4464-92f3-04ab071393c0
Copilot AI review requested due to automatic review settings August 14, 2026 16:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

plugins/mobile-apps/scripts/dataverse-request.js:545

  • Retry-After handling in the new BATCH-METADATA retry loop can compute delayMs as NaN when the header is present but not a simple integer (e.g., an HTTP-date or malformed value). setTimeout(..., NaN) effectively becomes immediate, which can cause tight retry loops during throttling and amplify rate-limiting instead of backing off.
    if (res.statusCode === 429 && attempt < maxRetries) {
      const retryAfter = res.headers?.['retry-after'];
      const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 30000;
      rateLimited = true;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
      continue;

plugins/mobile-apps/agents/screen-planner.md:399

  • The related_entity_fields schema constrains cardinality to "1:1" | "1:many" | "M:N", but the derivation table uses "direct lookup primary display" as a cardinality value. This makes the guidance self-contradictory and could cause downstream agents to emit invalid YAML.
  | `archetype_class` | `cardinality` | `recommends` |
  |---|---|---|
  | `list`, `tab-root`, `dashboard` | direct lookup primary display | `formatted-lookup` |
  | `list`, `tab-root`, `dashboard` | any other `1:1` related field | `external-projection-required` |
  | `detail` | `1:1` | `chained-fetch` |

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 78870709-25fe-45d7-8669-dd0ba9470807
Copilot AI review requested due to automatic review settings August 18, 2026 04:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (4)

plugins/mobile-apps/scripts/lib/derived-metadata.js:147

  • In the computed-column comparison, actual.formulaDefinition.trim() / expected.formulaDefinition.trim() can throw if either value is present-but-non-string (malformed normalized metadata). This turns a fail-closed compatibility check into a hard crash instead of returning compatible: false with a reason.
    if (!expected.formulaDefinition) {
      reasons.push('expected computed metadata must include an exact FormulaDefinition');
    } else if (!actual.formulaDefinition) {
      reasons.push('live computed metadata did not return FormulaDefinition');
    } else if (actual.formulaDefinition.trim() !== expected.formulaDefinition.trim()) {

plugins/mobile-apps/scripts/validate-derived-metadata.js:28

  • compareDerivedMetadata(expected.columns || expected, actual.columns || actual) will throw (e.g. map is not a function) if either JSON file is an object that lacks a columns array. Since this is a CLI validation barrier, it should fail with a clear error message when the input shape is wrong.
  const args = parseArgs(process.argv);
  const expected = readJson(args.expected, '--expected');
  const actual = readJson(args.actual, '--actual');
  const results = compareDerivedMetadata(expected.columns || expected, actual.columns || actual);
  const incompatible = results.filter((result) => !result.compatible);

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:26

  • NODE_OPTIONS: --require=${fakeAzPreload}will break if the repo path contains spaces (NODE_OPTIONS is parsed like a command line). Quote the preload path so tests run reliably on common local paths likeC:\Users<Name>...`.
function fakeAzEnv(overrides = {}) {
  return {
    ...process.env,
    NODE_OPTIONS: `--require=${fakeAzPreload}`,
    FAKE_AZ_STATIC_TOKEN: 'test-token',
    ...overrides,
  };

plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js:60

  • NODE_OPTIONS: --require=${FAKE_AZ_PRELOAD}` can fail when the preload path contains spaces because NODE_OPTIONS is tokenized like a command line. Quoting the path makes the test harness more robust on Windows/macOS local paths.
  const result = spawnSync(process.execPath, ['-e', script], {
    encoding: 'utf8',
    env: {
      ...process.env,
      NODE_OPTIONS: `--require=${FAKE_AZ_PRELOAD}`,
      FAKE_AZ_LOG: logPath,

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 78870709-25fe-45d7-8669-dd0ba9470807
Copilot AI review requested due to automatic review settings August 18, 2026 05:09
Shubham Agarwal added 2 commits August 18, 2026 10:41
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 78870709-25fe-45d7-8669-dd0ba9470807
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 78870709-25fe-45d7-8669-dd0ba9470807

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (4)

plugins/mobile-apps/skills/add-dataverse/SKILL.md:152

  • The script invocation contract lists supported HTTP methods as GET/POST/PATCH/DELETE, but dataverse-request.js also supports PUT (and the skill later uses BATCH-METADATA). This mismatch can cause confusion when following the contract verbatim.
- Three positional args, in order: `<envUrl>`, `<METHOD>` (GET / POST / PATCH / DELETE), `<apiPath>` (everything after `/api/data/v9.2/`).

plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js:58

  • The tests set NODE_OPTIONS to --require=<path> without quoting the path or preserving any existing NODE_OPTIONS. If the repo path contains spaces (common on Windows/macOS user profiles), Node will parse the value incorrectly and the preload won't load, making the tests flaky on developer machines.
      NODE_OPTIONS: `--require=${FAKE_AZ_PRELOAD}`,

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:23

  • The test helper overwrites NODE_OPTIONS with an unquoted --require=<path>. If the path contains spaces, Node won't load the preload. It also clobbers any existing NODE_OPTIONS, which can make local runs behave differently from CI.
    NODE_OPTIONS: `--require=${fakeAzPreload}`,

plugins/mobile-apps/shared/samples/src/utils/dataverse.ts:82

  • normalizeDataverseGuid() lowercases and strips braces, but it doesn’t trim whitespace. A valid GUID like " {A...} " will currently return undefined, which defeats the function’s purpose for user- or URL-supplied IDs.
  if (!value) return undefined;
  const normalized = value.replace(/[{}]/g, '').toLowerCase();
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(normalized)
    ? normalized
    : undefined;

Copilot AI review requested due to automatic review settings August 18, 2026 05:14
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 78870709-25fe-45d7-8669-dd0ba9470807

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js:16

  • The header comment still says the tests “place a fake az executable first on PATH”, but the implementation now uses NODE_OPTIONS=--require=<fake-az-preload.js> to intercept execFileSync('az', ...). This is misleading when debugging failures or porting the approach to new tests.
// These tests exercise the real code path rather than mocking internals: a fake
// `az` executable is placed first on PATH and logs every invocation, so we can
// assert exactly which subcommands ran. The resource URL points at 127.0.0.1:1
// so that if the challenge probe IS reached it fails instantly with
// ECONNREFUSED instead of touching the network.

Comment thread plugins/mobile-apps/scripts/dataverse-request.js Outdated
Copilot AI review requested due to automatic review settings August 18, 2026 05:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (4)

plugins/mobile-apps/scripts/lib/derived-metadata.js:160

  • compareDerivedMetadata can throw if actualColumns contains null/non-object entries or rows missing table/logicalName (e.g., malformed derived-metadata-live.json). This should fail closed (mark incompatible) rather than crash the validator.
  for (const column of actualColumns || []) {
    const key = `${column.table}:${column.logicalName}`;
    if (actualByKey.has(key)) duplicateKeys.add(key);
    actualByKey.set(key, column);
  }

plugins/mobile-apps/scripts/check-routes.js:290

  • check-routes can report the same underlying collision twice (e.g., .tsx together with /index.tsx triggers both file-folder-route-collision and duplicate-route), inflating findings and making the output noisy. Consider suppressing duplicate-route when the conflict is an index-vs-file pair already covered by the file/folder collision finding.
      if (dests[route]) {
        duplicateRouteFindings.push({
          route,
          file,
          otherFile: dests[route].file,

plugins/mobile-apps/scripts/tests/dataverse-request-metadata-batch.test.js:26

  • NODE_OPTIONS uses an unquoted --require path. If the repo path contains spaces (common on Windows dev machines), Node can mis-parse NODE_OPTIONS and fail to load the preload, causing these tests to hit the real Azure CLI. Quoting the preload path makes this robust.
  return {
    ...process.env,
    NODE_OPTIONS: `--require=${fakeAzPreload}`,
    FAKE_AZ_STATIC_TOKEN: 'test-token',
    ...overrides,

plugins/mobile-apps/scripts/tests/auth-token-resolution.test.js:60

  • NODE_OPTIONS uses an unquoted --require path. If the repo path contains spaces, Node can mis-parse NODE_OPTIONS and fail to load the preload, which would make the test depend on a real az installation/session. Quote the preload path to keep tests hermetic.
    env: {
      ...process.env,
      NODE_OPTIONS: `--require=${FAKE_AZ_PRELOAD}`,
      FAKE_AZ_LOG: logPath,
      // Cleared unless a test opts in — the ambient shell may have them set.

@@ -0,0 +1,186 @@
'use strict';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used for metadata comparison? Do we need it, llms are good at text comparisions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This is the deterministic pre-write metadata comparison used by  validate-derived-metadata.js  in  /add-dataverse  Step 4a.
It compares normalized machine-readable semantics—not descriptive text—including exact lookup targets, Choice/Boolean integer mappings and labels,  SourceType ,  SourceTypeMask , column types, duplicate rows, and exact computed  FormulaDefinition .
The LLM plans the schema, but this script is a safety barrier before Dataverse mutation. It must be repeatable, testable, and fail closed; an LLM comparison would be nondeterministic and could incorrectly approve an incompatible existing column. The behavior is covered by the derived-metadata regression tests.

I agree with this if you still feel this wrong we can remove.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 07:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (1)

plugins/mobile-apps/scripts/dataverse-request.js:545

  • In runOneMetadataOperation, the 429 (throttling) path computes delayMs from Retry-After but never actually waits before retrying. This can cause an immediate tight retry loop that worsens throttling and defeats the intended backoff behavior.
    if (res.statusCode === 429 && attempt < maxRetries) {
      const retryAfter = res.headers?.['retry-after'];
      const delaySeconds = retryAfter ? Number.parseInt(String(retryAfter), 10) : NaN;
      const delayMs = Number.isFinite(delaySeconds) ? delaySeconds * 1000 : 30000;
      rateLimited = true;
      continue;
    }

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 341594d3-c514-4ede-b3d4-4112f67c35c8
Copilot AI review requested due to automatic review settings August 18, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (2)

plugins/mobile-apps/scripts/dataverse-request.js:545

  • In runOneMetadataOperation, 429 handling computes delayMs from Retry-After but immediately retries without waiting. This effectively busy-loops on throttling and can worsen rate limiting in real Dataverse metadata runs (unlike the single-op path above, which sleeps).
    if (res.statusCode === 429 && attempt < maxRetries) {
      const retryAfter = res.headers?.['retry-after'];
      const delaySeconds = retryAfter ? Number.parseInt(String(retryAfter), 10) : NaN;
      const delayMs = Number.isFinite(delaySeconds) ? delaySeconds * 1000 : 30000;
      rateLimited = true;
      continue;
    }

plugins/mobile-apps/scripts/check-routes.js:263

  • The file/folder collision scan runs for every *.tsx except _layout.tsx, but it doesn’t skip files that fileToRoute() intentionally maps to null (e.g. +not-found.tsx). That can produce findings with route: null and fail the gate for files that are explicitly out of scope for routing checks.
  for (const file of screenFiles) {
    const relative = path.relative(appRoot, file).replace(/\\/g, '/');
    const base = relative.replace(/\.tsx$/, '');
    const children = screenFiles.filter(other => {
      const otherRelative = path.relative(appRoot, other).replace(/\\/g, '/');

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants