Preserve state keys#1897
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
License agreement recorded@McHersheys, your agreement has been recorded and you have been added to contributors.md. All future PRs from your account will pass this check automatically. |
📝 WalkthroughWalkthroughState list APIs now return keyed entries instead of value-only arrays across engine adapters, SDK interfaces, and console normalization. Console parsing accepts multiple response shapes and preserves semantic keys, with tests updated across the stack. ChangesState list keyed entry flow
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
console/packages/console-frontend/src/api/state/state.test.ts (1)
32-39: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAdd a regression case for
value.keybeating a positional map key.This test only proves plain map keys are used. It would not catch the
/statesregression where an object entry likeitem-0masks an embedded semantickey.Proposed test addition
it('uses map keys for object-shaped item responses', async () => { mockStateItems({ items: { 'files/conscience/covenant.yaml': { raw: true } } }) const { items } = await fetchStateItems('chambers') expect(items[0].key).toBe('files/conscience/covenant.yaml') expect(items[0].value).toEqual({ raw: true }) }) + + it('uses explicit item keys before object entry keys', async () => { + mockStateItems({ items: { 'item-0': { key: 'worker', value: { status: 'ready' } } } }) + + const { items } = await fetchStateItems('chambers') + + expect(items[0].key).toBe('worker') + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@console/packages/console-frontend/src/api/state/state.test.ts` around lines 32 - 39, Add a regression test case to the test file that verifies when a value object contains its own `key` property, the actual map key is still correctly used and not overridden by the embedded key. Extend or create a new test in the state.test.ts file that mocks a response where an item value contains a `key` property (for example, `{ raw: true, key: 'some-other-key' }`), and assert that `items[0].key` resolves to the map key (the object property name) and not the embedded `value.key` property. This regression test should prevent regressions like the `/states` issue where an embedded `key` property could incorrectly mask the positional map key.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@console/packages/console-frontend/src/api/state/state.ts`:
- Around line 33-35: The stateItemValue function only unwraps item.value when
item.key or item.state_key are non-empty strings, but the code elsewhere accepts
item.id as a valid key source. Update the condition in the stateItemValue
function to also check nonEmptyString(item.id) alongside the existing checks for
item.key and item.state_key, so that the value is properly unwrapped regardless
of which key source is used.
- Around line 76-80: In the state.ts file within the map function that processes
rawItems, the priority order for selecting a key is incorrect. The code
currently uses "key || embeddedKey || fallbackKey(index)" which prioritizes the
Object.entries key before the embedded value.key property. Change the return
statement in makeStateItem to prioritize embeddedKey (which already checks
value.key first via nonEmptyString calls) over the map entry key, so the order
becomes "embeddedKey || key || fallbackKey(index)" instead. This ensures that an
explicit value.key property in the record takes precedence over the map entry
key before falling back to other alternatives.
---
Nitpick comments:
In `@console/packages/console-frontend/src/api/state/state.test.ts`:
- Around line 32-39: Add a regression test case to the test file that verifies
when a value object contains its own `key` property, the actual map key is still
correctly used and not overridden by the embedded key. Extend or create a new
test in the state.test.ts file that mocks a response where an item value
contains a `key` property (for example, `{ raw: true, key: 'some-other-key' }`),
and assert that `items[0].key` resolves to the map key (the object property
name) and not the embedded `value.key` property. This regression test should
prevent regressions like the `/states` issue where an embedded `key` property
could incorrectly mask the positional map key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d38bc76f-66d8-4bf9-bd67-c3fdcb5b499e
📒 Files selected for processing (2)
console/packages/console-frontend/src/api/state/state.test.tsconsole/packages/console-frontend/src/api/state/state.ts
| function stateItemValue(item: Record<string, unknown>): unknown { | ||
| if ('value' in item && (nonEmptyString(item.key) || nonEmptyString(item.state_key))) { | ||
| return item.value |
There was a problem hiding this comment.
Unwrap value consistently when id is used as the key.
Line 63 accepts item.id as a key source, but Line 34 only unwraps item.value for key/state_key. { id: 'worker', value: {...} } would render the wrapper object instead of the actual state value.
Proposed fix
function stateItemValue(item: Record<string, unknown>): unknown {
- if ('value' in item && (nonEmptyString(item.key) || nonEmptyString(item.state_key))) {
+ if (
+ 'value' in item &&
+ (nonEmptyString(item.key) || nonEmptyString(item.state_key) || nonEmptyString(item.id))
+ ) {
return item.value
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function stateItemValue(item: Record<string, unknown>): unknown { | |
| if ('value' in item && (nonEmptyString(item.key) || nonEmptyString(item.state_key))) { | |
| return item.value | |
| function stateItemValue(item: Record<string, unknown>): unknown { | |
| if ( | |
| 'value' in item && | |
| (nonEmptyString(item.key) || nonEmptyString(item.state_key) || nonEmptyString(item.id)) | |
| ) { | |
| return item.value | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/packages/console-frontend/src/api/state/state.ts` around lines 33 -
35, The stateItemValue function only unwraps item.value when item.key or
item.state_key are non-empty strings, but the code elsewhere accepts item.id as
a valid key source. Update the condition in the stateItemValue function to also
check nonEmptyString(item.id) alongside the existing checks for item.key and
item.state_key, so that the value is properly unwrapped regardless of which key
source is used.
95908ac to
fc62c41
Compare
|
Thanks for the PR! We'll take a look and get back to you |
|
Hey @McHersheys do you mind to update your branch? I tested you code and got this result: The reason this is happening is that the engine is not returning the key. You can confirm this by running this: Do you mind to check this, since the main issue was not addressed? |
fc62c41 to
5eb320a
Compare
|
@McHersheys is attempting to deploy a commit to the motia Team on Vercel. A member of the Team first needs to authorize it. |
|
Thanks, you were right — the engine list path was still returning values without their state keys. I updated the branch so Verified locally:
One caveat: |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@engine/src/workers/state/adapters/bridge.rs`:
- Around line 89-98: In the state list conversion logic in bridge.rs, the
current unwrapping of result["items"] in the bridge adapter can incorrectly
treat a real state entry named "items" as an envelope. Update the result
handling in the state listing path (around the object-to-StateListItem mapping)
so that "items" is only unwrapped when the payload is clearly wrapped, and
otherwise preserve the original top-level key/value map unchanged. Keep the
existing StateListItem conversion behavior, but make the envelope detection
explicit before accessing the items field.
In `@sdk/packages/rust/iii/tests/state.rs`:
- Around line 233-244: The test in state::list is too lenient because it only
asserts arr.len() >= items.len() and then zips the sorted arrays, which can hide
extra unexpected rows; change the count check to require an exact match between
arr and items so the test fails on leaks or regressions. Keep the existing
sorting and entry-by-entry assertions in the same test scope, but make the
length assertion strict using the arr and items collections before the zip loop.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 34e4e936-5b22-4663-8a64-57b5e9a88a51
📒 Files selected for processing (17)
console/packages/console-frontend/src/api/state/state.test.tsconsole/packages/console-frontend/src/api/state/state.tsengine/src/builtins/kv.rsengine/src/workers/state/adapters/bridge.rsengine/src/workers/state/adapters/kv_store.rsengine/src/workers/state/adapters/mod.rsengine/src/workers/state/adapters/redis_adapter.rsengine/src/workers/state/state.rsengine/src/workers/state/structs.rsengine/src/workers/state/trigger.rssdk/packages/node/iii-browser/src/state.tssdk/packages/node/iii-example/src/state.tssdk/packages/node/iii/src/state.tssdk/packages/node/iii/tests/state.test.tssdk/packages/python/iii/src/iii/state.pysdk/packages/python/iii/tests/test_state.pysdk/packages/rust/iii/tests/state.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- console/packages/console-frontend/src/api/state/state.test.ts
- console/packages/console-frontend/src/api/state/state.ts
| let result = result.get("items").cloned().unwrap_or(result); | ||
|
|
||
| if let Some(object) = result.as_object() { | ||
| return Ok(object | ||
| .iter() | ||
| .map(|(key, value)| StateListItem { | ||
| key: key.clone(), | ||
| value: value.clone(), | ||
| }) | ||
| .collect()); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Handle top-level "items" without shadowing a real state key.
Line 89 unwraps result["items"] before checking whether the whole payload is already a plain key/value map. That drops or relabels legitimate state entries whose actual key is "items", which breaks the “preserve semantic keys” contract for arbitrary scopes. Please only treat items as an envelope when the response shape is unambiguously wrapped.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@engine/src/workers/state/adapters/bridge.rs` around lines 89 - 98, In the
state list conversion logic in bridge.rs, the current unwrapping of
result["items"] in the bridge adapter can incorrectly treat a real state entry
named "items" as an envelope. Update the result handling in the state listing
path (around the object-to-StateListItem mapping) so that "items" is only
unwrapped when the payload is clearly wrapped, and otherwise preserve the
original top-level key/value map unchanged. Keep the existing StateListItem
conversion behavior, but make the envelope detection explicit before accessing
the items field.
| assert!(arr.len() >= items.len()); | ||
|
|
||
| let mut result_sorted = arr.clone(); | ||
| result_sorted.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str())); | ||
| result_sorted.sort_by(|a, b| a["key"].as_str().cmp(&b["key"].as_str())); | ||
|
|
||
| let mut items_sorted = items.clone(); | ||
| items_sorted.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str())); | ||
|
|
||
| assert_eq!(result_sorted, items_sorted); | ||
| for (entry, item) in result_sorted.iter().zip(items_sorted.iter()) { | ||
| assert_eq!(entry["key"], item["id"]); | ||
| assert_eq!(entry["value"], *item); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Restore an exact assertion for the returned entry count.
Line 233 only checks >=, and Lines 241-244 use zip, so extra unexpected rows are silently ignored. This scope is generated per test, so the assertion can stay strict and catch leaks/regressions in state::list.
Suggested tightening
- assert!(arr.len() >= items.len());
+ assert_eq!(arr.len(), items.len());
let mut result_sorted = arr.clone();
result_sorted.sort_by(|a, b| a["key"].as_str().cmp(&b["key"].as_str()));
let mut items_sorted = items.clone();
items_sorted.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str()));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert!(arr.len() >= items.len()); | |
| let mut result_sorted = arr.clone(); | |
| result_sorted.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str())); | |
| result_sorted.sort_by(|a, b| a["key"].as_str().cmp(&b["key"].as_str())); | |
| let mut items_sorted = items.clone(); | |
| items_sorted.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str())); | |
| assert_eq!(result_sorted, items_sorted); | |
| for (entry, item) in result_sorted.iter().zip(items_sorted.iter()) { | |
| assert_eq!(entry["key"], item["id"]); | |
| assert_eq!(entry["value"], *item); | |
| } | |
| assert_eq!(arr.len(), items.len()); | |
| let mut result_sorted = arr.clone(); | |
| result_sorted.sort_by(|a, b| a["key"].as_str().cmp(&b["key"].as_str())); | |
| let mut items_sorted = items.clone(); | |
| items_sorted.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str())); | |
| for (entry, item) in result_sorted.iter().zip(items_sorted.iter()) { | |
| assert_eq!(entry["key"], item["id"]); | |
| assert_eq!(entry["value"], *item); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sdk/packages/rust/iii/tests/state.rs` around lines 233 - 244, The test in
state::list is too lenient because it only asserts arr.len() >= items.len() and
then zips the sorted arrays, which can hide extra unexpected rows; change the
count check to require an exact match between arr and items so the test fails on
leaks or regressions. Keep the existing sorting and entry-by-entry assertions in
the same test scope, but make the length assertion strict using the arr and
items collections before the zip loop.


Fixes #1896.
Tests:
corepack pnpm --filter console-frontend test -- src/api/state/state.test.tscorepack pnpm --filter console-frontend lintcorepack pnpm --filter console-frontend buildI license my contributions to this repository under Apache 2.0, and I have all necessary rights over the code I am contributing.
Summary by CodeRabbit
key+value) instead of value-only arrays.