Repository: kjdev/php-ext-zstd
Pull request #81: Fix/streaming uncompress dict
Author: login: kjdev
Created: 2025-05-25T22:09:02Z | Merged: 2025-05-25T22:42:23Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a new test to verify streaming compression and decompression with dictionaries using the zstd PHP extension.

- **Bug Fixes**
  - Improved support for streaming decompression with dictionaries, including better handling of unknown frame content sizes and enhanced error management.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/tests/dictionary_02.phpt b/tests/dictionary_02.phpt
new file mode 100644
index 0000000..f4faa68
--- /dev/null
+++ b/tests/dictionary_02.phpt
@@ -0,0 +1,33 @@
+--TEST--
+zstd_uncompress_dict(): streaming decompression with dictionary
+--SKIPIF--
+<?php
+if (LIBZSTD_VERSION_NUMBER < 10304) die("skip needs libzstd 1.3.4");
+?>
+--FILE--
+<?php
+include(dirname(__FILE__) . '/data.inc');
+$dictionary = file_get_contents(dirname(__FILE__) . '/data.dic');
+
+$context = stream_context_create(
+    array(
+        'zstd' => array(
+            'dict'  => $dictionary
+        )
+    )
+);
+
+$file = dirname(__FILE__) . '/data_' . basename(__FILE__, ".php") . '.out';
+file_put_contents('compress.zstd://' . $file, $data, 0, $context);
+$enc = file_get_contents($file);
+
+$dec  = zstd_uncompress_dict($enc, $dictionary);
+
+var_dump($data === $dec);
+
+@unlink($file);
+?>
+===Done===
+--EXPECTF--
+bool(true)
+===Done===
diff --git a/zstd.c b/zstd.c
index 9c9fc86..1e6d4c2 100644
--- a/zstd.c
+++ b/zstd.c
@@ -543,51 +543,100 @@ ZEND_FUNCTION(zstd_uncompress_dict)
     char *input, *dict;
     size_t input_len, dict_len;
     zend_string *output;
+    uint8_t streaming = 0;
+    size_t result;
+    unsigned long long size;
+    ZSTD_DCtx *dctx;
+    ZSTD_DDict *ddict;
 
     ZEND_PARSE_PARAMETERS_START(2, 2)
         Z_PARAM_STRING(input, input_len)
         Z_PARAM_STRING(dict, dict_len)
     ZEND_PARSE_PARAMETERS_END();
 
-    unsigned long long const rSize = ZSTD_getFrameContentSize(input,
-                                                              input_len);
-
-    if (rSize == 0 || rSize == ZSTD_CONTENTSIZE_ERROR) {
+    size = ZSTD_getFrameContentSize(input, input_len);
+    if (size == 0) {
+        RETURN_EMPTY_STRING();
+    } else if (size == ZSTD_CONTENTSIZE_ERROR) {
         ZSTD_WARNING("it was not compressed by zstd");
         RETURN_FALSE;
+    } else if (size == ZSTD_CONTENTSIZE_UNKNOWN) {
+        streaming = 1;
+        size = input_len + ZSTD_DStreamOutSize();
     }
 
-    ZSTD_DCtx* const dctx = ZSTD_createDCtx();
+    dctx = ZSTD_createDCtx();
     if (dctx == NULL) {
-        ZSTD_WARNING("ZSTD_createDCtx() error");
+        ZSTD_WARNING("failed to prepare uncompression");
         RETURN_FALSE;
     }
-    ZSTD_DDict* const ddict = ZSTD_createDDict(dict,
-                                               dict_len);
+    ddict = ZSTD_createDDict(dict, dict_len);
     if (!ddict) {
-        ZSTD_freeDStream(dctx);
-        ZSTD_WARNING("ZSTD_createDDict() error");
+        ZSTD_freeDCtx(dctx);
+        ZSTD_WARNING("failed to load dictionary");
         RETURN_FALSE;
     }
 
-    output = zend_string_alloc(rSize, 0);
+    output = zend_string_alloc(size, 0);
 
-    size_t const dSize = ZSTD_decompress_usingDDict(dctx, ZSTR_VAL(output), rSize,
-                                                    input,
-                                                    input_len,
-                                                    ddict);
-    if (dSize != rSize) {
-        ZSTD_freeDStream(dctx);
-        ZSTD_freeDDict(ddict);
-        zend_string_efree(output);
-        ZSTD_WARNING("%s", ZSTD_getErrorName(dSize));
-        RETURN_FALSE;
+    if (!streaming) {
+        result = ZSTD_decompress_usingDDict(dctx, ZSTR_VAL(output), size,
+                                            input, input_len, ddict);
+        if (ZSTD_IS_ERROR(result)) {
+            zend_string_efree(output);
+            ZSTD_WARNING("%s", ZSTD_getErrorName(result));
+            RETVAL_FALSE;
+        } else if (result != size) {
+            zend_string_efree(output);
+            ZSTD_WARNING("failed to uncompress");
+            RETVAL_FALSE;
+        } else {
+            output = zstd_string_output_truncate(output, result);
+            RETVAL_NEW_STR(output);
+        }
+    } else {
+        ZSTD_inBuffer in = { NULL, 0, 0 };
+        ZSTD_outBuffer out = { NULL, 0, 0 };
+        size_t chunk = ZSTD_DStreamOutSize();
+
+        ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only);
+        ZSTD_DCtx_refDDict(dctx, ddict);
+
+        in.src = input;
+        in.size = input_len;
+        in.pos = 0;
+
+        out.dst = ZSTR_VAL(output);
+        out.size = size;
+        out.pos = 0;
+
+        while (in.pos < in.size) {
+            if (out.pos == out.size) {
+                out.size += chunk;
+                output = zend_string_extend(output, out.size, 0);
+                out.dst = ZSTR_VAL(output);
+            }
+
+            result = ZSTD_decompressStream(dctx, &out, &in);
+            if (ZSTD_IS_ERROR(result)) {
+                zend_string_efree(output);
+                ZSTD_freeDCtx(dctx);
+                ZSTD_freeDDict(ddict);
+                ZSTD_WARNING("%s", ZSTD_getErrorName(result));
+                RETURN_FALSE;
+            }
+
+            if (result == 0) {
+                break;
+            }
+        }
+
+        output = zstd_string_output_truncate(output, out.pos);
+        RETVAL_NEW_STR(output);
     }
+
     ZSTD_freeDCtx(dctx);
     ZSTD_freeDDict(ddict);
-
-    output = zstd_string_output_truncate(output, dSize);
-    RETVAL_NEW_STR(output);
 }
 
 ZEND_FUNCTION(zstd_compress_init)


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>zstd.c (1)</summary><blockquote>

`582-596`: **Consider preventing potential double-free issues.**

While the current code flow is correct, it would be safer to set `output = NULL` after `efree(output)` to prevent any potential double-free issues if the code is modified in the future.

Apply this diff to improve safety:

```diff
 if (ZSTD_IS_ERROR(result)) {
     zend_string_efree(output);
+    output = NULL;
     ZSTD_WARNING("%s", ZSTD_getErrorName(result));
     RETVAL_FALSE;
 } else if (result != size) {
     zend_string_efree(output);
+    output = NULL;
     ZSTD_WARNING("failed to uncompress");
     RETVAL_FALSE;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 044cc55d49936fd57fbfc6f07b2731b43cfd6af0 and 79afa0419ad280e4a7d290fa7d8f024b847b6e13.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `tests/dictionary_02.phpt` (1 hunks)
* `zstd.c` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>zstd.c (4)</summary>

`546-550`: **LGTM! Well-structured variable declarations.**

The new variables are appropriately typed and necessary for implementing streaming decompression support.

---

`557-566`: **Good implementation of frame content size handling.**

The three-way logic correctly handles all possible scenarios:
- Empty frames (size == 0)
- Invalid frames (ZSTD_CONTENTSIZE_ERROR)
- Streaming frames (ZSTD_CONTENTSIZE_UNKNOWN)

The initial buffer size estimation for streaming mode is reasonable.

---

`568-578`: **Excellent error handling and resource management.**

The code properly creates the decompression context and dictionary with appropriate error messages and cleanup on failure.

---

`597-636`: **Excellent streaming decompression implementation.**

The streaming mode correctly:
- Initializes input/output buffers
- References the dictionary using the streaming API
- Handles dynamic buffer expansion
- Includes comprehensive error handling with proper resource cleanup
- Breaks the loop when decompression is complete (result == 0)

</details>
<details>
<summary>tests/dictionary_02.phpt (1)</summary>

`9-10`: <details>
<summary>✅ Verification successful</summary>

**Verify that required test data files exist.**

The test depends on `data.inc` and `data.dic` files. Please ensure these files are present in the test directory.

Run the following script to verify the test data files:

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Verify that required test data files exist for the dictionary test.

# Test: Check if data.inc exists
fd -t f "data.inc" tests/

# Test: Check if data.dic exists
fd -t f "data.dic" tests/

# Test: List all dictionary-related test files to understand the test structure
fd -t f "dictionary" tests/
```

Length of output: 181

---



</details>

**Required test data files verified**

Both `tests/data.inc` and `tests/data.dic` are present in the `tests/` directory, satisfying the test’s dependencies. No further action needed.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-25T22:12:16Z
user: login: kjdev
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-25T22:23:27Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-25T22:23:46Z

Review comments:
diff_hunk: @@ -0,0 +1,33 @@
+--TEST--
+zstd_uncompress_dict(): streaming compress
path: tests/dictionary_02.phpt
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Fix test description to accurately reflect the test purpose.**

The current description "streaming compress" is incomplete and doesn't accurately describe what the test does.

Apply this diff to fix the test description:

```diff
 --TEST--
-zstd_uncompress_dict(): streaming compress
+zstd_uncompress_dict(): streaming decompression with dictionary
 --SKIPIF--
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
--TEST--
zstd_uncompress_dict(): streaming decompression with dictionary
--SKIPIF--
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In tests/dictionary_02.phpt at lines 1 to 2, the test description "streaming
compress" is inaccurate and incomplete. Update the test description to clearly
and accurately reflect that the test is for "zstd_uncompress_dict(): streaming
decompress" or a similarly precise phrase that matches the test's actual
purpose.
```

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-25T22:12:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,33 @@
+--TEST--
+zstd_uncompress_dict(): streaming compress
path: tests/dictionary_02.phpt
user: login: kjdev
body: fixed
created_at: 2025-05-25T22:23:27Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,33 @@
+--TEST--
+zstd_uncompress_dict(): streaming compress
path: tests/dictionary_02.phpt
user: login: coderabbitai[bot]
body: @kjdev Thank you for fixing the test description! The updated description will now accurately reflect that the test is verifying streaming decompression with dictionary functionality.

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20spotted%20an%20unclear%20test%20description%20and%20suggested%20a%20precise%20fix%3A%20update%20%22streaming%20compress%22%20to%20%22streaming%20decompression%20with%20dictionary%22%20for%20clarity%20and%20accuracy!%20%F0%9F%9B%A0%EF%B8%8F%20Try%20it!&url=https%3A%2F%2Fgithub.com%2Fkjdev%2Fphp-ext-zstd%2Fpull%2F81%23discussion_r2106319841)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@kjdev Thank you for fixing the test description! The updated description will now accurately reflect that the test is verifying streaming decompression with dictionary functionality.

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20spotted%20an%20unclear%20test%20description%20and%20suggested%20a%20precise%20fix%3A%20update%20%22streaming%20compress%22%20to%20%22streaming%20decompression%20with%20dictionary%22%20for%20clarity%20and%20accuracy!%20%F0%9F%9B%A0%EF%B8%8F%20Try%20it!&url=https%3A%2F%2Fgithub.com%2Fkjdev%2Fphp-ext-zstd%2Fpull%2F81%23discussion_r2106319841)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-25T22:23:46Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-25T22:09:08Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

A new test file was added to verify streaming compression and decompression with dictionaries using the zstd PHP extension. Additionally, the `zstd_uncompress_dict` function was refactored to support streaming decompression when the frame content size is unknown, improving error handling and output buffer management.

## Changes

| File(s)                 | Change Summary                                                                                       |
|------------------------|----------------------------------------------------------------------------------------------------|
| tests/dictionary_02.phpt | Added a new test for streaming compression and decompression with dictionaries using the zstd extension. |
| zstd.c                 | Refactored `zstd_uncompress_dict` to support streaming decompression for unknown frame sizes, improved error handling, and adjusted output buffer management. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant TestScript
    participant ZstdExtension
    participant Libzstd

    TestScript->>ZstdExtension: Compress data with dictionary (streaming)
    ZstdExtension->>Libzstd: Compress using provided dictionary
    Libzstd-->>ZstdExtension: Compressed data
    ZstdExtension-->>TestScript: Write compressed data to file

    TestScript->>ZstdExtension: Decompress data with dictionary (streaming)
    ZstdExtension->>Libzstd: Decompress (handle unknown frame size)
    Libzstd-->>ZstdExtension: Decompressed data (streaming)
    ZstdExtension-->>TestScript: Return decompressed data
```

## Poem

> In the warren where data flows free,  
> A new test hops in, as bright as can be.  
> Dictionaries in streams, now handled with care,  
> Decompressing unknowns—no need to despair!  
> With buffers that grow and errors that warn,  
> This rabbit’s code leaps, robust and reborn.  
> 🐇✨
"""

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 79afa0419ad280e4a7d290fa7d8f024b847b6e13 and 8ed886572a135f430333f50486fe7768f91dde8f.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `tests/dictionary_02.phpt` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* tests/dictionary_02.phpt

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (17)</summary>

* GitHub Check: ci (8.3-zts, php)
* GitHub Check: ci (8.3-zts, apcu, php)
* GitHub Check: ci (8.1, x86, ts, vs16, true, apcu)
* GitHub Check: ci (8.4, x64, ts, vs17, false)
* GitHub Check: ci (8.1, x64, ts, vs16, true, apcu)
* GitHub Check: ci (8.3, x86, nts, vs16, true)
* GitHub Check: ci (8.1, x64, nts, vs16, true)
* GitHub Check: ci (8.4, x86, ts, vs17, false)
* GitHub Check: ci (8.1, x64, nts, vs16, false, apcu)
* GitHub Check: ci (8.1, x64, nts, vs16, true, apcu)
* GitHub Check: ci (8.3, x86, ts, vs16, false)
* GitHub Check: ci (8.3, x86, ts, vs16, true)
* GitHub Check: ci (8.3, x86, nts, vs16, false, apcu)
* GitHub Check: ci (8.3, x64, nts, vs16, true, apcu)
* GitHub Check: ci (8.4, x86, nts, vs17, true)
* GitHub Check: ci (8.3, x64, ts, vs16, true)
* GitHub Check: ci (8.4, x86, ts, vs17, true)

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=kjdev/php-ext-zstd&utm_content=81):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: kjdev
created_at: 2025-05-25T22:09:23Z
body: @coderabbitai review

author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-05-25T22:09:27Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE
user: login: kjdev
created_at: 2025-05-25T22:33:33Z
body: @coderabbitai review

author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-05-25T22:33:39Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE