Skip to content

.NET: Fix single-column value unwrap in declarative workflow#6367

Open
peibekwe wants to merge 2 commits into
mainfrom
peibekwe/declarative-fix-foreach-regression
Open

.NET: Fix single-column value unwrap in declarative workflow#6367
peibekwe wants to merge 2 commits into
mainfrom
peibekwe/declarative-fix-foreach-regression

Conversation

@peibekwe
Copy link
Copy Markdown
Contributor

@peibekwe peibekwe commented Jun 5, 2026

Motivation and Context

Power Fx wraps scalar array literals like =[1, 2, 3] as Table({Value: 1}, {Value: 2}, {Value: 3}).

This PR preserves the multi-field fix and additionally unwraps the single-column {Value: ...} shape so Local.LoopValue reads as the scalar the author wrote. Mirrors the convention already applied in DataValueExtensions.ToObject.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

@peibekwe peibekwe self-assigned this Jun 5, 2026
Copilot AI review requested due to automatic review settings June 5, 2026 22:37
@moonbox3 moonbox3 added .NET workflows Related to Workflows in agent-framework labels Jun 5, 2026
@github-actions github-actions Bot changed the title Fix single-column value unwrap in declarative workflow .NET: Fix single-column value unwrap in declarative workflow Jun 5, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 adjusts how ForeachExecutor materializes loop items when the items expression evaluates to a Power Fx single-column table produced by scalar array literals (e.g. =[1,2,3]), unwrapping the {Value: ...} record shape so the loop variable receives the scalar value as-authored.

Changes:

  • Update ForeachExecutor to unwrap single-column RecordDataValue items with the "Value" field into their scalar FormulaValue.
  • Add a unit test covering the scalar-array-literal table shape to ensure the loop variable is not a RecordValue and converts to the expected scalar.

Reviewed changes

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

File Description
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/ForeachExecutor.cs Unwraps {Value: ...} loop items into scalars when materializing table-backed foreach values.
dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs Adds regression coverage for single-column Value record unwrapping behavior.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 90%

✓ Correctness

The PR correctly implements unwrapping of Power Fx's single-column {Value: ...} wrapper records in the foreach loop executor. The three-part condition (is RecordDataValue, has exactly one property, that property is named "Value") is precise and does not interfere with multi-field records. The logic is consistent with the existing unwrap pattern in DataValueExtensions.ToObject (lines 210-213). No correctness issues found.

✓ Security Reliability

The change introduces a well-guarded single-column Value-record unwrap in ForeachExecutor that mirrors the existing convention in DataValueExtensions.ToObject. The logic is correct: it only unwraps when the record has exactly one property named "Value", leaving multi-field records and non-record values unchanged. The null safety is sound (TryGetValue returning true guarantees non-null out parameter per .NET contracts). Checkpoint/restore is unaffected since the unwrapped FormulaValue[] is persisted after transformation. No security or reliability issues found.

✓ Test Coverage

The new test ForeachTakeNextWithSingleColumnValueRecordAsync correctly verifies the happy-path unwrapping of single-column {Value: ...} records. The existing ForeachTakeNextWithMultiFieldRecordAsync covers the multi-field pass-through. However, there's no test for the edge case of a single-column record whose key is NOT "Value" (e.g., {Name: "Alice"}), which exercises a distinct branch in the new ToLoopValue logic (Properties.Count == 1 is true but TryGetValue("Value", ...) is false).

✗ Design Approach

The new unwrap helper conflates Power Fx single-column scalar tables with ordinary record tables whose schema is literally { Value: ... }. Existing tests already treat { Value: string } rows as legitimate records, so applying this unconditionally in ForeachExecutor changes the meaning of valid inputs and can break CurrentValue.Value lookups inside foreach bodies.

Flagged Issues

  • ForeachExecutor now unwraps every single-field Value record before publishing the loop item (lines 104-108), but the repo already uses { Value: ... } as a real record shape (e.g., EditTableV2ExecutorTest assertions at lines 163-177 and 251-267). A foreach over that same table shape will silently hand the body a scalar instead of a record, breaking expressions like CurrentValue.Value in the loop body.

Automated review by peibekwe's agents

@peibekwe peibekwe marked this pull request as ready for review June 5, 2026 22:57
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 91%

✓ Correctness

The PR correctly implements single-column Value-record unwrapping for Power Fx scalar array literals in the foreach loop executor. The logic mirrors the existing convention in DataValueExtensions.ToObject (line 210), correctly falls through to value.ToFormula() for multi-field records (count != 1) or records with a single field named something other than "Value", and is null-safe since ToFormula is an extension method on DataValue? that returns blank for null. No correctness issues found.

✓ Security Reliability

The change is sound from a security and reliability standpoint. The new ToLopValue method mirrors the established single-column Value unwrap pattern already present in DataValueExtensions.ToObject (line 210). The guard conditions (exact count check + TryGetValue) are defensive, ToFormula is a null-safe extension method, and no external/untrusted input flows through this path without validation. The previously-raised checkpoint-restore inconsistency concern was already resolved in the review thread.

✓ Test Coverage

The new test ForeachTakeNextWithSingleColumnValueRecordAsync adequately covers the primary use case (Power Fx {Value: N} unwrap to scalar), and the existing ForeachTakeNextWithMultiFieldRecordAsync confirms multi-field records pass through. However, there is a gap: no test verifies that a single-field record whose key is NOT "Value" (e.g., {name: "Alice"}) remains as a RecordValue. This edge case validates the key-name check in ToLoopValue and would catch regressions if the condition were accidentally broadened.

✗ Design Approach

The design change fixes the fresh-execution path, but it still leaves checkpoint restore on the old shape. That means the same foreach can expose a scalar on a new run and a {Value: ...} record on resume after restore, so I would request changes until the normalization is applied consistently across both entry points.

Flagged Issues

  • ForeachExecutor only unwraps single-column Value records when _values is materialized from Model.Items; OnCheckpointRestoredAsync still restores _values with savedValues.Select(value => value.ToFormula()) (ForeachExecutor.cs:138), so resumed workflows can still observe wrapped loop values even though fresh executions now return scalars. Please apply the same single-column unwrapping logic to restored values.

Automated review by peibekwe's agents

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

Labels

.NET workflows Related to Workflows in agent-framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants