Make A2UI dataModelUpdate.valueArray first-class#805
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 25, 2026, 4:08 PM ET / 20:08 UTC. Summary Reproducibility: yes. Source inspection of current main shows docs require Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Mantis proof suggestion Next step before merge
Security Review detailsBest possible solution: Land the focused protocol-parity fix after normal maintainer review, keeping Do we have a high-confidence way to reproduce the issue? Yes. Source inspection of current main shows docs require Is this the best way to solve the issue? Yes. Extending the existing typed-value parser, store depth guard, redactor, and focused A2UI tests is the narrow maintainable fix for the documented protocol behavior. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 7a7f39df2397. Label changesLabel justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
c224d26 to
12d2492
Compare
c962797 to
a171762
Compare
The v0.8 `dataModelUpdate.valueArray` typed value was silently dropped by the WinUI parser: `DataModelEntry` had no `ValueArray` field and `ToJsonNode()` returned null for it. Only `valueString/Number/Boolean/valueMap` were handled. This broke seeding an array into a surface's data model (e.g. a multi-select `MultipleChoice` bound to a path), even though `valueArray` is part of the v0.8 protocol (docs/a2ui/protocol.md §2.2 and data-and-actions.md). Changes: - Parser (A2UIProtocol.cs): add `DataModelEntry.ValueArray`; `ToJsonNode()` emits a `JsonArray`; new `ParseValueArray`/`ParseArrayElement` handle value-typed object elements, bare primitives (`["a",1,true]`), nested maps/arrays, and preserve JSON null as a stable index slot. - DoS guard (DataModelStore.cs): the 32-deep depth bound now recurses `valueArray` as well as `valueMap`. - Security (SecretRedactor.cs): the array branch now redacts registered/ denylisted element paths instead of only recursing, so a secret seeded into an array (e.g. an obscured field bound to /codes/0) no longer leaks via canvas.a2ui.dump. - Docs (SKILL.md): correct the stale "arrays are not first-class" note. Tests: parser coverage for valueArray (strings, mixed scalars, maps, nested arrays, bare primitives, empty, null slots) plus valueMap/scalar regressions; secret-redaction regressions for secrets inside arrays; store-level base-path landing and depth-guard rejection; and an end-to-end MultipleChoice surface seeded via valueArray that asserts both the snapshot and the rendered preselection. Validation: build.ps1 (all projects), Shared.Tests, Tray.Tests, and A2UI UITests all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reject non-canonical array pointer indices for data-model array access, notify descendant subscribers when container values are replaced, and redact registered secret descendants when action contexts or snapshots include parent paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
a171762 to
1ffb6bc
Compare
Summary
The v0.8
dataModelUpdate.valueArraytyped value was silently dropped by the native WinUI A2UI parser —DataModelEntryhad noValueArrayfield andToJsonNode()returnednullfor it. OnlyvalueString/valueNumber/valueBoolean/valueMapwere handled.This broke seeding an array into a surface's data model — most visibly a multi-select
MultipleChoicebound to a path could never be pre-populated — even thoughvalueArrayis part of the v0.8 protocol (docs/a2ui/protocol.md§2.2 anddocs/a2ui/data-and-actions.md).What changed
A2UIProtocol.cs): addDataModelEntry.ValueArray;ToJsonNode()emits a realJsonArray; newParseValueArray/ParseArrayElementhandle value-typed object elements ({"valueString":"x"}), bare-primitive tolerance (["a",1,true]), nested maps/arrays, and preserve a JSONnull(or value-less{}) as a stable index slot so position-sensitive consumers don't see shifted indices.DataModelStore.cs): the existing 32-deep depth bound now recursesvalueArrayas well asvalueMap, so deep nesting can't be smuggled in through an array.SecretRedactor.cs): theJsonArrayredaction branch now checks each element path against the registered/denylist set before recursing (mirroring the object branch). Previously arrays only recursed, so a secret seeded into an array (e.g. an obscuredTextFieldbound to/codes/0) could leak throughcanvas.a2ui.dump.SKILL.md): correct the stale "arrays are not first-class" note.Why it's safe
valueMapbehavior is unchanged and regression-tested.System.Text.JsonTryGetValue<T>is strict (no cross-kind coercion), so the bare-primitive string→bool→number detection can't mis-tag a value.Tests
A2UIDataModelArrayTests.cs(new) — parser coverage: strings, mixed scalars, maps, nested arrays, bare primitives, empty, and null/value-less slots, plusvalueMap/scalar regressions.SecretRedactorTests.cs— array-redaction regressions: registered element, registered array parent, denylisted scalar inside an array.A2UIDataModelStoreTests.cs(new) — store base-path landing +valueArraydepth-guard rejection +valueMapdepth regression.A2UIControlMatrixTests.cs— end-to-end:MultipleChoice(multi) seeded viavalueArrayasserts both the snapshot array and the rendered ListView preselection (parser → store → renderer round-trip).Validation
./build.ps1(all projects),OpenClaw.Shared.Tests,OpenClaw.Tray.Tests, and the A2UIOpenClaw.Tray.UITestsall pass (OPENCLAW_REPO_ROOTset per AGENTS.md).Proof
I ran temporary proof-only harnesses locally to print the actual behavior, then removed those harnesses so they are not part of this PR.
Parser + redaction behavior
Input behavior exercised:
{"dataModelUpdate":{"surfaceId":"s","path":"/form","contents":[{"key":"picked","valueArray":[{"valueString":"red"},{"valueString":"blue"}]}]}}Terminal output from the proof run:
This proves the parser stores a real JSON array at the scoped path and that a registered secret array element is redacted instead of leaking through dump/snapshot output.
WinUI rendered state behavior
The WinUI proof seeded a multi-select
MultipleChoicewith:{"dataModelUpdate":{"surfaceId":"s","contents":[{"key":"picked","valueArray":[{"valueString":"g"},{"valueString":"b"}]}]}}Terminal output from the proof run:
This proves the value flows through parser → data model store → surface snapshot → native WinUI renderer: the snapshot carries the JSON array and the rendered
ListViewhas Green/Blue preselected.Known limitations (pre-existing, out of scope)
valueMapbehavior; a shared element-count budget would be a separate change.BuildActionContextdeep-clones a non-secret subtree without redacting nested secret-named descendants — pre-existing, applies tovalueMaptoo, not introduced here.