Skip to content

Record the creator on a workflow's sharing record - #2300

Open
DarshitChanpura wants to merge 7 commits into
opensearch-project:mainfrom
DarshitChanpura:fix/workflow-resource-sharing-context
Open

DarshitChanpura wants to merge 7 commits into
opensearch-project:mainfrom
DarshitChanpura:fix/workflow-resource-sharing-context

Conversation

@DarshitChanpura

@DarshitChanpura DarshitChanpura commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Description

Bug fix. Under resource sharing, creating a workflow wrote no sharing record, so the workflow's creator could not find it by search, and it could not be shared.

TransportIndexWorkflowAction captured the caller's thread context inside validateMonitorAccess's listener, which runs after scope.launch on Dispatchers.IO. Coroutines do not carry the thread context, so the captured context held no user. The security plugin then logged "no authenticated user found in the thread context" and skipped the record. When an earlier request had left a context on the same worker, the record could name that request's user instead: in one test run, a workflow Alice created was recorded as Bob's. The capture placement came from PR 2180. The monitor action captures before its coroutine, and works.

The context is now captured in doExecute, on the request thread, before the coroutine starts. It is applied only around the workflow write, through a new withStoredContext helper that afterwards restores the coroutine's own context. Restoring it without that scope made workflow create fail with "User doesn't have permissions to execute this action": the metadata and monitor lookups after the write then ran as the user against the system index, instead of as the plugin.

The other context captures that PR 2180 added all run before any thread change.

Known, and left for a follow-up: the monitor create, update and delete actions restore the caller's context on a Dispatchers.IO worker and never reset it. As a result, a capture made inside a coroutine picks up whatever an earlier request left there. That is why a regression of this fix shows up in the tests some of the time rather than every time.

Related Issues

Needs PR opensearch-project/security#6627, now merged and published in the security snapshot. Without it, the security plugin resolves the workflow's type as workflow, which matches no provider, so no sharing record is written either way. The two workflow tests below pass only with both changes. The two changes should be released together: without this one, the security change makes workflow records whose owner can be wrong.

Testing

The integration tests, the context fix, and a matchesResource override on the workflow provider. In PR 6627 the security plugin asks each provider whether a document is of its type, through ResourceProvider.matchesResource, which by default compares the typeField with the type name. Workflows are registered as alerting-workflow but store workflow in workflow.type, so the override matches on that value; it compiles only against a security SPI that has the method.

  • SecureResourceSharingMonitorRestApiIT adds tests that follow all_shared_principals through share, update and revoke for monitors and workflows. They also check that search returns exactly what a user owns or is currently shared. With a local security build carrying PR 6627: 33 of 33, on main and on 3.9. The node log no longer shows "no authenticated user found" for workflows.
  • With the capture moved back inside the coroutine, the two workflow tests failed in one run, one with the record naming the wrong user. They passed in another run, for the reason given above.
  • Without the matchesResource override, the two workflow tests fail and the other 31 pass.
  • With this branch merged with main, against the published security snapshot that has PR 6627, the resource-sharing suite that CI runs (SecureResourceSharingMonitorRestApiIT and RscMigrateE2ERestApiIT, -Dsecurity=true -Dhttps=true -Dresource_sharing.enabled=true): 61 of 61.
  • WorkflowRestApiIT: 30 of 30, both without security and on a secured cluster with resource sharing off. SecureWorkflowRestApiIT is marked AwaitsFix, so it does not run.
  • The fix cherry-picks cleanly onto 3.9, hence the backport 3.9 label. The backport needs the security 3.9 backport, PR [Backport 3.9] Resolve a resource's type from its provider security#6628, which adds matchesResource there.

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Adds secure integration tests that follow all_shared_principals through
share, update and revoke for monitors and for workflows, and that check
search returns exactly the monitors and workflows a user owns or is
currently shared, replacing the two earlier search tests.

The workflow tests need the security plugin to resolve the workflow
resource type and alerting to record the workflow's creator, so they
pass only once both are fixed.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
TransportIndexWorkflowAction captured the caller's thread context inside
a coroutine on Dispatchers.IO, which does not carry the thread context.
The captured context held no user, so under resource sharing the
security plugin wrote no sharing record for a new workflow, and the
creator could not see it in search. When an earlier request had left a
context on that worker, the record could name that request's user
instead.

The context is now captured in doExecute, on the request thread, before
the coroutine starts. It is applied only around the workflow write,
through a new withStoredContext helper that restores the coroutine's own
context afterwards, so the metadata and monitor lookups that follow
still run as the plugin rather than as the caller.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit bb19a0f)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Possible Context Leak on Exception

In indexWithContext, stored.restore() is called outside the try/finally. If stashContext() throws (or any step between stored.restore() and threadContext.stashContext()), the caller's restored context remains on the current thread without being replaced by own, leaking the caller's auth onto an IO worker that a later request may reuse. Consider moving stored.restore() inside the try block so own.restore() in finally always runs after stored.restore().

suspend fun Client.indexWithContext(stored: ThreadContext.StoredContext, request: IndexRequest): IndexResponse {
    val threadContext = threadPool().threadContext
    return suspendUntil { listener ->
        val own = threadContext.newStoredContext(false)
        stored.restore()
        try {
            threadContext.stashContext().use { index(request, listener) }
        } finally {
            own.restore()
        }
    }
}

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Latest suggestions up to bb19a0f

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid consuming the stored context once

stored.restore() consumes the StoredContext, so a second invocation (e.g. the
retry/update path reusing the same storedThreadContext) will restore an empty
context and the security plugin will not see the caller. Re-capture the stored
context before restoring, or require/document single-use semantics; otherwise the
second write silently loses the caller identity.

alerting/src/main/kotlin/org/opensearch/alerting/util/SdkClientExtensions.kt [74-85]

 suspend fun Client.indexWithContext(stored: ThreadContext.StoredContext, request: IndexRequest): IndexResponse {
     val threadContext = threadPool().threadContext
     return suspendUntil { listener ->
         val own = threadContext.newStoredContext(false)
-        stored.restore()
         try {
+            stored.restore()
+            val reusable = threadContext.newStoredContext(false)
             threadContext.stashContext().use { index(request, listener) }
+            reusable.restore() // no-op, but keeps `stored` effectively reusable upstream
         } finally {
             own.restore()
         }
     }
 }
Suggestion importance[1-10]: 3

__

Why: ThreadContext.StoredContext.restore() in OpenSearch is actually reusable (it saves and restores on close), so the claim that it's one-shot is not accurate. The suggested improved_code also doesn't materially change the behavior.

Low
Stored context may be consumed once

storedContext is captured outside the coroutine but is restored inside
indexWithContext twice (create and update paths both call it). Since
ThreadContext.StoredContext.restore() is one-shot, the second usage (the update
branch after retry/validation failure path) will restore a drained context and lose
the caller — breaking the sharing-record creator attribution on update. Capture a
fresh StoredContext for each indexWithContext call site.

alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt [229-232]

+val tenantId = client.threadPool().threadContext.getHeader(AlertingPlugin.TENANT_ID_HEADER)
+// Capture before launch: Dispatchers.IO drops the thread context, and the workflow write needs the caller's auth
+val storedContext = client.threadPool().threadContext.newStoredContext(false)
+scope.launch(TenantContext(tenantId)) {
 
-
Suggestion importance[1-10]: 2

__

Why: Same premise as suggestion 1 — the assumption that StoredContext.restore() drains the context is incorrect for OpenSearch's ThreadContext.StoredContext. Also, create vs update paths are mutually exclusive in a single request flow, so re-capture is unnecessary. The improved_code is identical to the existing_code.

Low

Previous suggestions

Suggestions up to commit f3dcd02
CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid reusing a single-use StoredContext

ThreadContext.StoredContext.restore() is documented as single-use and marks itself
closed after the first call; here stored is restored on every invocation of
withStoredContext, and in TransportIndexWorkflowAction the same storedContext
captured before scope.launch is used by both the create and update paths. The second
use will fail or restore an already-closed context. Capture the context fresh per
write (or use a reusable snapshot), or document/guarantee single-use at the call
site.

alerting/src/main/kotlin/org/opensearch/alerting/util/SdkClientExtensions.kt [71-79]

 suspend fun <T> ThreadContext.withStoredContext(stored: ThreadContext.StoredContext, block: suspend () -> T): T {
     val own = newStoredContext(false)
-    stored.restore()
     try {
+        stored.restore()
         return block()
     } finally {
         own.restore()
     }
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion raises a legitimate concern about StoredContext.restore() being single-use, which could affect the update path reusing the same captured context. However, the improved_code is essentially identical to the existing_code (only moving stored.restore() inside the try block) and does not actually fix the reuse issue it describes.

Low
Suggestions up to commit d88ce61
CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid consuming reusable stored context

ThreadContext.StoredContext.restore() is typically single-use/not idempotent, and
calling stored.restore() here consumes the caller-provided storedContext. Because
IndexWorkflowHandler is reused across both the create and update code paths (and
storedThreadContext is captured once per request), the second invocation of
withStoredContext will restore an already-closed context. Capture a fresh snapshot
or re-stash before restoring so the stored context remains reusable.

alerting/src/main/kotlin/org/opensearch/alerting/util/SdkClientExtensions.kt [71-79]

 suspend fun <T> ThreadContext.withStoredContext(stored: ThreadContext.StoredContext, block: suspend () -> T): T {
     val own = newStoredContext(false)
-    stored.restore()
     try {
+        stored.restore()
         return block()
     } finally {
         own.restore()
     }
 }
Suggestion importance[1-10]: 3

__

Why: The suggestion's improved_code is functionally equivalent to the existing_code (moving stored.restore() inside the try block does not solve the alleged reuse problem). Also, in the PR's actual usage, IndexWorkflowHandler only calls withStoredContext once per request (either create or update path, not both), so the concern about double-consumption may not apply. Low impact and the proposed fix doesn't address the stated issue.

Low
Suggestions up to commit e13ec29
CategorySuggestion                                                                                                                                    Impact
General
Guard stored-context lifecycle against exceptions

ThreadContext.StoredContext.restore() is documented as single-use; calling
own.restore() in finally after block() may fail if the coroutine resumed on a
different thread and the stored context was already consumed/invalidated.
Additionally, if stored.restore() throws, own is never closed, leaking the captured
context. Capture own with try/finally that at minimum closes it, and consider using
AutoCloseable/close() semantics rather than restore() for the owning context.

alerting/src/main/kotlin/org/opensearch/alerting/util/SdkClientExtensions.kt [71-79]

 suspend fun <T> ThreadContext.withStoredContext(stored: ThreadContext.StoredContext, block: suspend () -> T): T {
     val own = newStoredContext(false)
-    stored.restore()
     try {
+        stored.restore()
         return block()
     } finally {
         own.restore()
     }
 }
Suggestion importance[1-10]: 5

__

Why: Moving stored.restore() inside the try/finally ensures own.restore() is still executed if stored.restore() throws, preventing a potential context leak. This is a reasonable defensive improvement, though the practical risk is low.

Low
Prevent leaking captured thread context

storedContext is now created unconditionally before scope.launch, but the previous
code only created it inside the validateMonitorAccess success callback. If
scope.launch throws or the coroutine aborts before IndexWorkflowHandler consumes
storedContext, the stored context is never restored/closed, leaking a
ThreadContext.StoredContext. Ensure the stored context is always closed on all exit
paths (e.g., in try/finally around the launched work or by closing it on early
returns/failures).

alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt [230-233]

 val tenantId = client.threadPool().threadContext.getHeader(AlertingPlugin.TENANT_ID_HEADER)
 // Capture before launch: Dispatchers.IO drops the thread context, and the workflow write needs the caller's auth
 val storedContext = client.threadPool().threadContext.newStoredContext(false)
 scope.launch(TenantContext(tenantId)) {
+    try {
+        // ... existing body that eventually consumes storedContext ...
+    } catch (t: Throwable) {
+        storedContext.close()
+        throw t
+    }
Suggestion importance[1-10]: 4

__

Why: The concern about leaking storedContext on early coroutine failure is valid in principle, but ThreadContext.StoredContext does not require explicit closing when unused, so the practical impact is limited. The improved code is also incomplete.

Low
Suggestions up to commit 60b457e
CategorySuggestion                                                                                                                                    Impact
General
Close captured context on all paths

storedContext is created unconditionally before scope.launch, but several
early-return branches inside the coroutine (e.g. trigger-count failure) now never
consume it. A StoredContext holds references to captured headers/transients; if it
is not restored/closed, the capture leaks. Ensure the stored context is closed on
every exit path (e.g. use try/finally around the launch body or close it in
early-return branches).

alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt [230-233]

 val tenantId = client.threadPool().threadContext.getHeader(AlertingPlugin.TENANT_ID_HEADER)
 // Capture before launch: Dispatchers.IO drops the thread context, and the workflow write needs the caller's auth
 val storedContext = client.threadPool().threadContext.newStoredContext(false)
 scope.launch(TenantContext(tenantId)) {
+    try {
+        // ... existing body, passing storedContext into IndexWorkflowHandler
+    } finally {
+        storedContext.close()
+    }
Suggestion importance[1-10]: 4

__

Why: Highlights a potential minor resource leak if the coroutine returns early without consuming storedContext. However, StoredContext is lightweight and the impact is low; the suggestion is reasonable but not critical.

Low
Ensure prior context is restored on failure

ThreadContext.StoredContext.restore() is documented to be callable only once, yet
own is created before stored.restore() runs and may itself be restored by the
StoredContext's own close/auto-restore semantics in callers. More importantly, if
block() suspends and resumes on a different thread, own.restore() will apply the
captured context to the wrong thread. Capture/restore using
ThreadContext.newStoredContext on the resuming thread or use stashContext() scoping
to ensure correctness across suspension boundaries.

alerting/src/main/kotlin/org/opensearch/alerting/util/SdkClientExtensions.kt [71-79]

 suspend fun <T> ThreadContext.withStoredContext(stored: ThreadContext.StoredContext, block: suspend () -> T): T {
     val own = newStoredContext(false)
-    stored.restore()
     try {
+        stored.restore()
         return block()
     } finally {
         own.restore()
     }
 }
Suggestion importance[1-10]: 3

__

Why: The suggestion's reordering is minor; newStoredContext itself does not throw in practice, so moving stored.restore() into the try block offers only marginal robustness. The broader concerns about cross-thread resume are speculative and not clearly addressed by the improved code.

Low
Suggestions up to commit 069a8a3
CategorySuggestion                                                                                                                                    Impact
Possible issue
Guard single-use stored context against reuse

storedContext is captured once and then passed into both the create and update code
paths which each call withStoredContext(storedContext) { ... }. Since
StoredContext.restore() can only be invoked once, if any retry, fallback, or
error-recovery flow re-enters withStoredContext with the same instance, the second
restore will fail. Ensure the handler consumes storedContext exactly once per
request, or re-capture per write.

alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt [230-233]

 val tenantId = client.threadPool().threadContext.getHeader(AlertingPlugin.TENANT_ID_HEADER)
-// Capture before launch: Dispatchers.IO drops the thread context, and the workflow write needs the caller's auth
+// Capture before launch: Dispatchers.IO drops the thread context, and the workflow write needs the caller's auth.
+// NOTE: this StoredContext is single-use; it must be restored exactly once downstream.
 val storedContext = client.threadPool().threadContext.newStoredContext(false)
 scope.launch(TenantContext(tenantId)) {
Suggestion importance[1-10]: 4

__

Why: The concern about reuse across create and update paths is partially valid, but in practice only one of the two paths executes per request. The suggestion only adds a comment, so the actual impact is limited.

Low
General
Document single-use contract of stored context

StoredContext.restore() is single-use and must not be invoked more than once; if
withStoredContext is called twice with the same stored instance (e.g., retry paths
or when the caller passes a shared context), the second restore() will throw or
corrupt the context. Additionally, if the coroutine resumes on a different thread,
own was captured on the original thread's context and restoring it on another thread
may not clear transient state as intended. Consider documenting the single-use
contract explicitly and/or re-capturing via newStoredContext instead of relying on
the passed-in stored.

alerting/src/main/kotlin/org/opensearch/alerting/util/SdkClientExtensions.kt [71-79]

 suspend fun <T> ThreadContext.withStoredContext(stored: ThreadContext.StoredContext, block: suspend () -> T): T {
+    // NOTE: `stored` is consumed here (StoredContext.restore() must be called at most once).
+    // Callers must not reuse the same StoredContext instance across multiple invocations.
     val own = newStoredContext(false)
     stored.restore()
     try {
         return block()
     } finally {
         own.restore()
     }
 }
Suggestion importance[1-10]: 3

__

Why: The suggestion only adds a comment documenting single-use behavior without a functional change. In OpenSearch, StoredContext typically implements AutoCloseable and restore() is designed for single use, so documenting is marginally helpful but low-impact.

Low

The security plugin now identifies a resource's type by the value its
typeField holds, which defaults to the registered type name. Workflows
are registered as alerting-workflow while their documents hold workflow
in workflow.type, so the workflow provider declares that value through
typeFieldValue(). Monitors store monitor and need no change.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 60b457e

Workflow documents hold "workflow" in workflow.type, while the type is
registered as alerting-workflow, so the workflow provider overrides
matchesResource to match on that value.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit e13ec29

…ce-sharing-context

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit d88ce61

@riysaxen-amzn riysaxen-amzn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The core fix looks right: capturing the context in doExecute, on the request thread, before scope.launch explains both the missing sharing record and the wrong-owner records. Making storedThreadContext non-null also fits, since the only call site now always passes it.

My main concern is inline on withStoredContext: as written, I think it can leave the caller's persistent auth header on the IO worker where the write started, which is the same leak class this PR is fixing. A suggested shape is in that comment.

On CI: the short failures (including the security-enabled resource-sharing job) are docker pull rate limits (toomanyrequests), so they never ran the code. The ARM failures (468 tests, 52 failed, region must be set before starting and OpenSearchSingleNodeTestCase.NODE is null) match main's Test Workflow run from Oct 21 exactly, and main has been green since Oct 27. So a rebase on main and a re-run should clear them, and would let SecureResourceSharingMonitorRestApiIT actually run.

It would also help to link an issue for the monitor create/update/delete restore-without-reset follow-up, since that leak is what makes a regression here intermittent.

* Runs [block] under [stored], then restores the context the coroutine had, on whichever thread it resumes. This keeps
* the caller's context from reaching the calls that follow [block].
*/
suspend fun <T> ThreadContext.withStoredContext(stored: ThreadContext.StoredContext, block: suspend () -> T): T {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can leave the caller's identity on a worker thread

ThreadContext is per thread, and block suspends. Tracing the write path:

  1. On IO worker A, this saves own, then stored.restore() puts the caller's context on A.
  2. indexStashed calls stashContext() on A. A stash drops transient headers but keeps persistent ones, and the caller's auth is a persistent header (the comment removed in this PR said the write relies on that). It then suspends on client.index.
  3. On Dispatchers.IO the coroutine may resume on worker B, so both stored.close() in indexStashed and own.restore() here run on B.
  4. Nothing resets A. It keeps the stash state, which still carries the caller's persistent auth header.

The next coroutine that captures context on A can then pick up this caller, which is the "Alice's workflow recorded as Bob's" symptom from the description. The KDoc's promise ("keeps the caller's context from reaching the calls that follow") holds for the coroutine, but not for thread A.

One way to avoid it is to do the restore, stash and dispatch synchronously on A before suspending, and reset A right away:

suspendCoroutine { cont ->
    val own = ctx.newStoredContext(false)
    stored.restore()
    try {
        ctx.stashContext().use { client.index(request, listenerFor(cont)) }
    } finally {
        own.restore()
    }
}

The listener resumes the coroutine, and no thread keeps the caller's context after this returns. I haven't reproduced the leak; this is from the ThreadContext semantics, so please correct me if the security plugin clears the persistent header somewhere I missed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right. stashContext() keeps persistent headers, so after the coroutine suspended, the thread that started the write kept the caller's persistent headers, and the thread it resumed on received the starting thread's context.

In bb19a0f I replaced withStoredContext and indexStashed with indexWithContext, which follows your shape: it sets the caller's context, stashes, dispatches the index request, and gives the calling thread its own context back before suspending. SdkClientExtensionsTests starts the write on one thread and answers it on another. It fails on the previous helper (the starting thread still held the caller's header) and passes now.

storedThreadContext?.restore()
try {
val indexResponse: IndexResponse = client.indexStashed(indexRequest)
val indexResponse: IndexResponse = client.threadPool().threadContext.withStoredContext(storedThreadContext) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Question: on update, this restores the context captured on the request thread, which is the updater's, not the creator's. That's fine if the security plugin only records createdBy on the first index of a doc. Could you confirm that an update by a user the workflow is shared with doesn't rewrite the owner? I couldn't tell from the new tests whether "update" there means updating the workflow doc or updating the sharing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed. The security plugin writes the sharing record only when the document is created. On a later write to the resource, ResourceIndexListener.postIndex only restores all_shared_principals from the record and does not change created_by.

"Update" in the existing test meant an update of the workflow document by the owner. In bb19a0f I added test workflow update by a read-write shared user keeps the owner: bob, with read-write access to the workflow and its delegate monitor, renames the workflow, and the sharing record still names alice as the owner.

override fun resourceType(): String = ResourceSharingUtils.WORKFLOW_RESOURCE_TYPE
override fun resourceIndexName(): String = SCHEDULED_JOBS_INDEX
override fun typeField(): String = "workflow.type"
override fun matchesResource(fieldValue: Function<String, String>): Boolean =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: this only compiles against a security SPI that has matchesResource (security#6627), and build.gradle doesn't change, so it relies on the current security snapshot. Since the description says the two must ship together, would a minimum security version check or a release-notes entry be worth adding so that dependency is enforced rather than only described?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Alerting compiles against the security SPI of its own OpenSearch version (opensearch-security-spi:${opensearch_build}), and the two ship in the same release. A build against an SPI without matchesResource does not compile, so that is the check. Release notes are generated from the PR metadata now, so the description carries the dependency.

…ce-sharing-context

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit f3dcd02

withStoredContext restored the caller's context, then indexStashed
stashed it and suspended on the write. A stash keeps persistent headers,
so the thread that started the write kept the caller's persistent
headers, and the thread the coroutine resumed on received the starting
thread's context when the stash and the stored context were closed.

indexWithContext replaces both. It sets the caller's context, stashes,
dispatches the index request, and gives the calling thread its own
context back before the coroutine suspends, so no thread keeps the
caller's context.

Adds a unit test that starts the write on one thread and answers it on
another, and an integration test that a read-write shared user's update
to a workflow keeps the owner on its sharing record.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura

Copy link
Copy Markdown
Member Author

Thanks for the review. I merged the current main into the branch, and the follow-up for the monitor, alert and comment paths is #2319. It also covers the *DataObjectStashed helpers, which close their stash on the completing thread.

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit bb19a0f

@codecov

codecov Bot commented Oct 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 67.83%. Comparing base (778e7ce) to head (bb19a0f).
⚠️ Report is 256 commits behind head on main.

Files with missing lines Patch % Lines
...earch/alerting/AlertingResourceSharingExtension.kt 0.00% 1 Missing ⚠️

❌ Your project check has failed because the head coverage (67.83%) is below the target coverage (70.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2300      +/-   ##
============================================
+ Coverage     67.72%   67.83%   +0.10%     
- Complexity      105     1813    +1708     
============================================
  Files           160      170      +10     
  Lines         10343    14322    +3979     
  Branches       1522     2088     +566     
============================================
+ Hits           7005     9715    +2710     
- Misses         2672     3727    +1055     
- Partials        666      880     +214     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

backport 3.9 Backport to 3.9 branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants