**supervision_value: 4**
**difficulty: 3**

**Reasoning:**
The task touches a core framework constraint with timing and concurrency semantics, requiring the agent to understand several pieces: how `DelayedConstraint` applies polling/delay, the existing `Timestamp` helpers, the relationship between the synchronous and asynchronous paths, and the fact that the `ref` overload duplicates logic that the base `Constraint.ApplyTo(ref TActual)` already handles by wrapping the value. The agent cannot simply rename or rearrange; it must reason about the timing model, decide to unify the sync paths (value, delegate, ref) into one loop, preserve the async path with mirroring but using `Task.Delay`, and clean up the now-unused `ThreadUtility.BlockingDelay`. The commit message gives direction but the agent still needs to trace through the overloads and confirm that removing the `ref` override is safe (the base class already does equivalent work). The resulting implementation is highly targeted, teaches multiple desirable behaviors (pattern recognition across similar code, simplifying via a shared helper, safe removal of dead code), and fits a compact but process-rich training example. Difficulty is medium because it requires connecting 3-4 facts across files and understanding both the old timestamp arithmetic and the new Stopwatch.Elapsed idiom, but it is well bounded.

---

**Implementation request**

### Summary

The `DelayedConstraint` (the `After` constraint) uses manual `Stopwatch.GetTimestamp`/`Frequency` arithmetic instead of the simpler `Stopwatch.Elapsed` property. It also duplicates the same polling‑and‑delay logic across three `ApplyTo` overloads: value, delegate, and ref. We want to simplify and de‑duplicate this code.

### Acceptance criteria

1. **Use `Stopwatch.Elapsed` for all timing.**
   Replace the hand‑rolled `TimestampOffset`/`TimestampDiff` arithmetic with `Stopwatch.Elapsed`. This makes the delay and polling calculations easier to read and less error‑prone.

2. **Reduce the number of blocking implementations to two: synchronous and asynchronous.**
   - One synchronous path that works for both `ApplyTo<TActual>(TActual actual)` and `ApplyTo<TActual>(ActualValueDelegate<TActual> del)`.
   - One asynchronous path for `ApplyToAsync<TActual>(Func<Task<TActual>> taskDel)`.
   Both paths should share the same delay logic, just using `Thread.Sleep` in the sync path and `Task.Delay` in the async path.

3. **The `ApplyTo(ref TActual actual)` overload should be removed.**
   Its timing logic is identical to the other sync overloads and the base `Constraint` class already provides a suitable default implementation for the `ref` variant, so the override is unnecessary.

4. **Remove the internal `ThreadUtility.BlockingDelay` method.**
   It was only used by the old code and is no longer referenced after the consolidation. Directly use `Thread.Sleep` and `Task.Delay` instead.

5. **Behaviour must remain unchanged.**
   The observable contract of the `After` constraint – waiting for a specified delay, optionally polling at an interval, and delegating to the base constraint – must be preserved exactly. No existing public API surface changes except the removal of the `ref` overload (which is a valid internal simplification because the base class behavior is identical).

Replace own TimeStamp calculations with Stopwatch.Elapsed.


```
Only unwrap `TargetInvocationException` when it originates from inside
NUnit.Framework itself, not from user code.

When a test method throws a `TargetInvocationException` (for example, because
the test uses `MethodInfo.Invoke` and the invoked method throws), the
exception-reporting logic currently unconditionally unwraps it to the inner
exception. This loses the outer exception's type, message, and stack trace,
making it look like the inner exception was thrown directly.

Preserve user-code `TargetInvocationException`s as the outermost exception
in the reported result. Only unwrap when the exception's stack trace shows
that the reflection call happened inside NUnit.Framework (e.g., during the
framework's own test-method invocation). When a user-code TIE is preserved,
the test-result message must contain the full exception chain, beginning with
`System.Reflection.TargetInvocationException` and showing the inner exception
as a nested inner.
```

Ony Unwrap TargetInvocationException if raised from inside NUnit.Framework


We should clean up a few reflection calls in the NUnit codebase where `.GetTypeInfo()` is called on a `System.Type` object merely to reach a method that is already directly available on `Type` itself. This simplification has no functional effect but reduces unnecessary indirection and aligns better with modern .NET APIs. The changes should be made in framework and test code, and all existing behavior must be preserved.

Specifically, check the logic that validates whether a given type is an attribute (i.e., the `IsAssignableFrom` check against `typeof(Attribute)`). In those cases the current code calls `typeof(Attribute).GetTypeInfo().IsAssignableFrom(expectedType.GetTypeInfo())`; it should just call `typeof(Attribute).IsAssignableFrom(expectedType)` directly. This validation appears in multiple constraint constructors.

Also look for places where test code retrieves a method via `typeof(SomeType).GetTypeInfo().GetMethods(...)`. That intermediate `.GetTypeInfo()` call is unnecessary because `Type.GetMethods` is available directly. Apply the same simplification there.

The acceptance criterion is that all existing tests pass and no behavior changes. The fix is purely a removal of redundant `.GetTypeInfo()` invocations; do not introduce any new APIs, change method names, or alter logic.


**Title:** `Randomizer.NextDecimal()` returns biased values, not uniformly distributed over the full decimal range

**Description**
When generating random decimals via `Randomizer.NextDecimal()` (the unbounded overload), the output is not uniformly distributed across the non-negative, scale‑0 decimal space. Empirically, the values are heavily skewed toward smaller numbers, and the maximum possible value (~7.9e28) is never produced. The same bias affects the bounded overload `NextDecimal(decimal max)` for large `max`.

This skew means tests using random decimal inputs will under‑represent high‑magnitude edge cases, reducing the effective coverage of property‑based tests or `Random` attribute‑driven test data where decimals are involved.

**Expected behavior**
`NextDecimal()` should return any representable non‑negative decimal with scale = 0 with equal probability. The bounded overload should produce a uniform distribution over `[0, max]` (scale = 0), with no systematic bias toward smaller values.

**Possible cause (from a quick look at the source)**
The three 32‑bit integer parts (`low`, `mid`, `high`) that form the decimal’s internal representation are each obtained via `Next(0, int.MaxValue)`. That method returns values in `[0, int.MaxValue)` – it excludes `int.MaxValue` itself. Because the `high` part never reaches its maximum, the overall 96‑bit integer can never reach the true decimal maximum, and the distribution is biased downward. The bounded version appears to have a similar truncation in the way it masks significant bits.

**Impact**
Any test that relies on `Randomizer.NextDecimal()` – whether called directly or through NUnit’s `Random`/`Range`/`ValueSource` attributes with decimal parameters – may miss high‑range values and distort statistical properties of generated test data.


**Request: Fix test execution order for `[Order]` attribute and restore alphabetical fixture ordering**

### Behavior criteria

1. **`[Order]` priority: ordered before unordered**
   When a test method carries the `[Order(int)]` attribute, it must execute **before** any test method that does *not* have an `[Order]` attribute, regardless of the values used. Currently, unordered tests appear to run first, which contradicts the expectation that explicit ordering takes precedence.

2. **Same-order stability**
   If multiple test methods share the same explicit order value, their relative execution order should be the order in which they are declared in the source (or, equivalently, whatever stable intra-suite order already exists). They must **not** be re-sorted alphabetically simply because their order values are equal.

3. **Unordered test order**
   Tests without an `[Order]` attribute should still execute in a deterministic order—alphabetically by name—but only *after* all explicitly ordered tests have run.

4. **Alphabetical fixture order**
   When an NUnit runner loads an assembly, the test fixtures (the top-level classes containing the test methods) should be executed in alphabetical order by their fully qualified name. This sorting appears to be missing; please restore it so that fixture execution order is consistent across runs.

### Constraints / Notes

- The sorting should handle nested test suites (fixtures within namespaces, etc.) consistently.
- Do **not** change the semantics of `[Order]` itself; only change the interaction between ordered and unordered items in the execution pipeline.
- The existing test discovery and loading behavior should remain unaffected.


When `TestOutput.ToXml()` serializes output text containing characters that are not valid in XML (e.g., the `\u001b` control character), the resulting XML is malformed and can throw an exception. The framework should produce well-formed escaped XML for all output values, similar to how it already escapes text when adding child elements via `AddElement`/`AddElementWithCDATA`. The escaping currently only happens in those methods but not when a `TNode`'s value is set through its constructor, which is the path used by `TestOutput.ToXml()`. Ensure that invalid XML characters are escaped at the point where a node’s value is assigned, so that all node creation paths produce valid XML without requiring callers to pre-escape.

Fix TestOutput XML serialization with invalid characters


**Title:** `-exclude` option with `NotFilter` does not correctly exclude tests based on ancestor matching

**Description:**
The NUnit console runner’s `-exclude` option (which uses a `NotFilter` internally) is not correctly applying the exclusion when a category filter is specified. A top-level test suite that contains an excluded fixture still gets selected for execution, and some tests within the excluded fixture also run. The expected behavior is that a test should be excluded if the negated category filter matches the test itself **or** any of its ancestor test suites. Additionally, the explicit-match logic of `NotFilter` appears to be inconsistent and should follow the same matching semantics.

**Acceptance Criteria:**
- When a `NotFilter` wraps a category filter, calling `Pass(test)` on a top-level suite returns `true` (i.e., it is not excluded) only if **neither** the suite itself **nor** any ancestor suite matches the base filter.
- `IsExplicitMatch` on the `NotFilter` correctly delegates to the base filter’s `Match` method, so explicit matching is consistent with the overall exclusion logic.
- The `MatchParent` utility used for ancestor matching must be accessible to `NotFilter` (currently it is `private` in the base `TestFilter` class).
- No existing filter tests break, and the corrected behavior is validated with the appropriate unit tests for `NotFilter`.

**Context:**
The bug manifests when running tests with a command like `nunit3-console --exclude:CategoryName ...`. The console runner constructs a `NotFilter` around a `CategoryFilter`, but the filter’s pass decision does not check whether any parent test suite matches the excluded category, causing the suite (and its children) to still be selected.

Fix NotFilter so -exclude works correctly


```markdown
**Title:** NullReferenceException when a parameterized test fixture receives `null` for a value-type constructor argument

**Affected areas:** Parameterized test fixture construction, implicit-argument conversion logic (`Reflect`)

**Observed behavior:**
If a test fixture is declared as `[TestFixture(null)]` and its only constructor takes a parameter of an ordinary value type (struct, not `Nullable<T>`), the framework throws an unhandled `NullReferenceException` while attempting to build the fixture. The fixture is never registered as a test, and depending on the runner this may crash the test session or be silently swallowed.

**Expected behavior:**
The framework should handle this case gracefully: the fixture should be marked as `NotRunnable` with the standard skip reason `"No suitable constructor was found"`, exactly as it does for other impossible argument-to-parameter assignments.

**Root cause suspicion:**
The implicit-conversion check (inside `Reflect`) that determines whether a `null` source value can be assigned to a target parameter incorrectly falls through to code paths that assume the source is non-null when the target type is a value type other than `Nullable<T>`. When the source is `null` and the target is a non-nullable value type, the conversion should be deemed impossible immediately, before any deeper inspection that might dereference a null handle.

**Reproduction sketch (not a full test):**
```csharp
public struct OrdinaryValueType {}

[TestFixture(null)]
public class BreaksOnBuild : object
{
    public BreaksOnBuild(OrdinaryValueType _) {}
}
```

**Constraints / acceptance criteria:**
- For a parameterized test fixture with a constructor whose sole argument is an ordinary struct and whose `TestFixture` argument is `null`, the fixture must end up in the `NotRunnable` state.
- The `SkipReason` property must contain `"No suitable constructor was found"` (exact string as used by the existing constructor-matching logic).
- There must be no NullReferenceException or any other unhandled exception thrown from framework code.
- Existing conversion behavior must remain unchanged when `null` is passed for reference-type or `Nullable<T>` parameters (those must still succeed).
- No other fixture or test should be affected.

**Priority:** Should-fix – prevents a hard crash/instability during test discovery.
```

Fix NRE in Reflect class found by nullability warnings


**Title:** Partition filter crashes on test names whose UTF-8 byte length exceeds 4096

**Description**
When using the test partitioning feature (e.g., through the NUnit console runner with `--partition`), the framework throws an uncaught exception if any test in the assembly has a name whose UTF-8 representation is longer than 4096 bytes.

This is particularly easy to trigger with parameterized tests whose arguments are very long strings, or with test names that contain multibyte characters (e.g., Chinese text) – a name that is well under the 4096-character limit can still exceed 4096 bytes because a single CJK character encodes as 3 bytes in UTF-8.

**Steps to reproduce**
- Create a parameterized test with an argument string whose UTF-8 byte length is > 4096 (either by using a very long ASCII string, or a shorter string of multibyte characters).
- Run the tests with partition filtering enabled (e.g., `--partition=1/3`).

**Expected behavior**
The partition filter should gracefully handle test names of any length. A reasonable approach would be to truncate the UTF-8 encoded name to a maximum byte length (e.g., the existing 4096-byte buffer) before computing the partition hash, rather than trying to encode the entire name and overflowing the buffer. This allows the test to still be assigned a partition reliably without errors.

**Impact**
The partition feature becomes unusable for assemblies that contain any test with a sufficiently long name. Large test suites that rely on partitioning must either rename tests or disable partitioning entirely. The fix ensures that long names cannot break the filter, making the feature robust for all test naming patterns and character sets.

Added trimming to ComputeHashValue to handle names over 4096 bytes. (#4941)


**Feature Request: Add `Assert.EnterMultipleScope()` for delegate-free multiple assertions**

## Problem
The existing `Assert.Multiple` overloads require passing a delegate (`TestDelegate` or `AsyncTestDelegate`). This forces users to wrap all multiple-assertion logic inside a lambda or method, which is inflexible and awkward for:

1. Complex test bodies with natural `await` points, loops, or conditional assertions that aren't easily expressed as a single delegate.
2. Situations where you want to interleave setup and assertions within the same multiple-assert block.
3. Code that uses `using` blocks or other scoped constructs — the delegate pattern does not compose well.

As a result, teams often avoid `Multiple` or write convoluted lambdas. There is also no protection today against forgetting to close a multiple-assert block (i.e., leaking the elevated assertion level), which can silently produce confusing test results.

## Proposed solution
Introduce a public method on `Assert` that returns an `IDisposable` to enter and exit multiple-assert mode. This would allow users to write:

```csharp
using (Assert.EnterMultipleScope())
{
    // assertions here accumulate failures
}
```

The returned disposable should:

- On disposal, if any assertion failures were recorded, throw a `MultipleAssertException` aggregating them.
- Support nesting: scopes must be disposed in reverse order; disposing out of order should be an error.
- Be safe to dispose more than once (multiple `Dispose` calls should be a no-op after the first).
- If a test method finishes without disposing the scope, the test should fail with an error indicating that active assertion scopes remained.

## Behavioral notes and edge cases
- **Exception handling within a scope**: If an exception (not an assertion failure) is thrown inside the scope, the `Dispose` call (from `using` or explicit) should still report the assertion failures that occurred up to that point, but it should *not* re-throw or append the caught exception. This keeps failure reporting simple and focused on assertion failures, even if it means losing the exception information. (The existing `Multiple` methods should behave identically when refactored to use the new scope.)
- **Async code**: The scope must work correctly within `async` methods, so entering and disposing across `await` boundaries should be safe (the scope captures the execution context upon entry). Users will write:

```csharp
using (Assert.EnterMultipleScope())
{
    await Task.Delay(100);
    Assert.That(x, Is.EqualTo(y));
}
```

- **Existing `Multiple` methods**: The delegate-based `Multiple` and `MultipleAsync` methods should be reimplemented on top of this new scope, ensuring consistency.

## Impact
This API would significantly improve the usability of multiple assertions in NUnit, aligning with modern C# patterns (`using` declarations, `IAsyncDisposable` in the future, etc.) and making the feature accessible for real-world test structures without forcing delegate wrappers.

**Bonus**: The explicit scope also prevents accidental leakage of the elevated assertion level guard, turning a potential source of silent confusion into a clear error.


**Title:** Assert.Multiple in TearDown throws even when its own assertions all pass, preventing completion of cleanup

**Description**

When a test method has already accumulated one or more assertion failures (so `PendingFailures > 0`), any subsequent `Assert.Multiple` block throws a `MultipleAssertException` at the end of the block **even when that block itself adds no new failed assertions**. This causes code after the block—for example, the remainder of a `[TearDown]` method—to be skipped entirely.

**Expected behavior**

- `Assert.Multiple` should only throw a `MultipleAssertException` when the block it encloses **produces new assertion failures**.
- If the block runs cleanly (all assertions pass) but the test already had pending failures from earlier stages (the test body, SetUp, etc.), the block should **not** throw. Execution after the block should continue normally.

**Concrete acceptance scenario**

1. A test method contains a failing assertion (or otherwise creates pending failures).
2. The test class has a `[TearDown]` that:
   - prints a start marker,
   - executes an `Assert.Multiple` block that contains **only passing assertions**,
   - prints an end marker,
   - and performs other teardown work.
3. When the test runs, the teardown’s after-`Multiple` code must execute completely. Users should observe the end marker and any subsequent cleanup.

In contrast, if the same `Assert.Multiple` block had contained a failing assertion, it would still be correct to throw at the close of the block (as it does today), because the block itself contributed new failures.

**Scope / constraints**

- The change should apply to **both synchronous and asynchronous overloads** of `Assert.Multiple` so behavior is consistent.
- The behavior when the `Multiple` block is nested inside another `Multiple` block must remain correct: the inner block should only contribute to the outer block’s decision, and the outer block should throw only when the **entire combined** set of blocks adds new failures. (Existing behavior for nesting is fine; just ensure the fix doesn’t break it.)
- The fix must not affect the case where `PendingFailures` is zero at block entry—the block should still throw when it registers new failures, and not throw when all assertions pass.

Accumulate further failures if any on AssertMultiple instead of throwing


[Title: ValueSourceAttribute throws NullReferenceException for inherited static property]

**Observed behavior:**
When a parameterized test method uses `[ValueSource("PropertyName")]` to reference a static property that is defined in a base class (not in the test fixture class itself), NUnit throws a `NullReferenceException`. The same scenario works correctly if the static property is defined directly in the test fixture class that contains the test method.

**Steps to reproduce:**
1. Define a base class (non-test-fixture) with a `protected static` property (or `public static`) that returns an `IEnumerable`.
2. Create a test fixture class that inherits from that base class.
3. In the derived fixture, write a parameterized test method whose parameter is decorated with `[ValueSource("InheritedStaticProperty")]` (the name of the property from the base class).
4. Run the test.

**Expected behavior:**
The test should execute, using the values provided by the inherited static property. The `ValueSourceAttribute` should locate static members defined in base classes just as it locates them in the declaring fixture class.

**Actual behavior:**
NUnit throws a `NullReferenceException` during discovery or execution of the test. The member is not found, which leads downstream to a null reference when trying to use the data source.

**Impact:**
This prevents sharing common value-source data across fixture inheritance hierarchies, forcing duplication of static properties in every derived fixture or workarounds that move the data outside the inheritance relationship.

add fix and test for NullRef for Inherited Property


When `Is.Empty` is applied to a null value that is neither a `string` nor a `DirectoryInfo`, the thrown `ArgumentException` simply states that the value must be a string or non-null `IEnumerable` or `DirectoryInfo`. The message gives no indication of what type the argument actually is, which makes diagnosing misuse unnecessarily difficult.

We should improve that error message to include the **declared type of the argument**, as determined by the generic type parameter passed to the constraint. The message should make it clear what type was considered “not a string, DirectoryInfo, or non-null IEnumerable.” This will help callers understand why the constraint rejected their value.

The change should be confined to the `EmptyConstraint` handling when `actual` is null.

Improve error message on EmptyConstraint #2928


**Feature Request: Modernize equality and comparison in core model types**

Several types in `NUnit.Framework.Interfaces` and `NUnit.Framework.Internal` override `Equals` and `GetHashCode` but do not implement `IEquatable<T>`. This causes unnecessary boxing, allocation, and slower comparisons when used in generic collections or when callers explicitly compare instances. Additionally, some of the existing `GetHashCode` implementations are fragile with respect to `null` string fields and/or rely on string formatting.

**Desired behavior / acceptance criteria:**

1. **`AssertionResult`**
   - Implement `IEquatable<AssertionResult>` so that direct comparisons between `AssertionResult` instances use a typed, efficient equality check.
   - The untyped `Equals(object)` must delegate to the typed `Equals`.
   - `GetHashCode` must be independent of `ToString()` and must not allocate intermediate formatted strings. Instead, combine the hash of each field (`Status`, `Message`, `StackTrace`) using a standard hash combination pattern (seed + multiplier).
   - The implementation must not throw when `Message` and/or `StackTrace` are `null`. Use `EqualityComparer<string>.Default.GetHashCode` (or equivalent null‑safe approach) for those fields.
   - The meaning of equality must not change: two results are equal when their `Status`, `Message`, and `StackTrace` are equal.

2. **`ResultState`**
   - Implement `IEquatable<ResultState>`; untyped `Equals(object)` delegates to typed `Equals`.
   - `GetHashCode` must be recomputed by combining the hash of `Status`, `Label`, and `Site` using the same safe pattern, and must handle `Label` being `null` without throwing.
   - The current `GetHashCode` implementation (`(int)Status << 8 + (int)Site ^ Label.GetHashCode()`) is incorrect when `Label` is `null` and is not distribution-friendly. The new implementation must not reproduce that flaw.
   - Equality semantics must remain unchanged: two `ResultState` instances are equal when their `Status`, `Label`, and `Site` are equal.

3. **`Test` (abstract class in `NUnit.Framework.Internal.Tests`)**
   - The `Test` class currently implements only non‑generic `IComparable` and compares by `FullName`. Add `IComparable<Test>` to the class to provide a strongly‑typed comparison.
   - The existing `CompareTo(object)` must delegate to the new typed `CompareTo(Test)`.
   - The typed `CompareTo` must return `-1` when the argument is `null`, otherwise delegate to `this.FullName.CompareTo(other.FullName)`. This preserves the existing ordering contract.

**Non‑goals / safety:**
- Do not change equality operators (`==` / `!=`); they are not defined.
- Do not change any serialization or XML‑visible contract.
- Do not introduce any behavior change in testing/assertion logic; equality and ordering must be identical to the current release except for the specific `null`‑safety fix in `ResultState.GetHashCode`.
- The implementation must compile against the existing framework target and not introduce new public API beyond the two `IEquatable<T>` interfaces and `IComparable<Test>`.

**Why this matters:**
- Generic collections (`Dictionary<K,V>`, `HashSet<T>`) and LINQ operations will avoid boxing and use direct typed equality calls.
- `GetHashCode` that relies on `ToString()` is brittle and allocates; replacing it improves performance and stability.
- `ResultState` hash currently throws for common “simple” states that don’t carry a `Label`, making it unusable in any hashing scenario.

Please ensure the changes are applied consistently across all three types.

Implement IEquatable and use recommended GetHashCode implementation


**Title:** OPA bundle persistence directory is not configurable

**Problem:**
When bundle persistence is enabled (e.g., via `bundles[_].persist`), bundles are always saved under `$PWD/.opa/bundles`. There is no way to override this location. In environments where the current working directory is not suitable for persistent data (read‑only filesystems, containerized deployments with writable volume mounts elsewhere, etc.) this makes persistence unusable or forces workarounds like symlinks.

**Expected behavior:**
OPA should support a new top‑level configuration key `persistence_directory` that sets the base directory for all persisted data (currently only bundles). If the key is omitted, the default path must remain `$PWD/.opa` to preserve backward compatibility. When the key is present, bundles should be persisted under `.../bundles` (e.g. `{persistence_directory}/bundles`). The configuration should be documented in the configuration reference.

**Impact:**
Without this change, operators cannot place bundle persistence on a separate volume or known writable location, which breaks deployment patterns where `/` is read‑only or `$PWD` is ephemeral. A top‑level `persistence_directory` option also leaves room for other types of persistent state to be stored under the same root in future releases, avoiding proliferation of per‑feature path settings.


Title: Add client certificate support and insecure TLS option to REST client

Description:
The REST client in `plugins/rest` (used for communicating with external services like bundle endpoints) currently only supports bearer token authentication. We need to extend it to support mutual TLS (client certificates) and, for testing/internal environments, an option to skip TLS server certificate verification.

**Configured behavior**
The service configuration JSON for a REST client should accept these new optional fields:
- `allow_insecure_tls` (boolean, default `false`): if `true`, disable verification of the server's TLS certificate.
- `credentials.client_tls` (object, optional):
  - `cert` (string, required when `client_tls` present): absolute or relative path to a PEM-encoded client certificate file.
  - `private_key` (string, required): path to the PEM-encoded private key file.
  - `private_key_passphrase` (string, optional): passphrase needed if the private key is encrypted.

When `client_tls` is present, the client must present the loaded certificate and key during the TLS handshake. Encrypted private keys must be handled: if the key is encrypted and no passphrase is provided, client creation must fail with a clear error. Likewise, any problem reading or parsing the certificate/key files must cause client creation to fail immediately, so misconfiguration is surfaced early.

**Interactions and invariants**
- The new fields must be completely optional; existing configurations that only use bearer tokens, headers, etc. must continue to work without modification.
- The `Do` method on the client should transparently use the TLS configuration constructed during `New`, with no special per-request changes needed.
- The insecure TLS flag and client certificate settings are independent; it must be possible to use client certificates while still verifying the server (the default), or to skip verification without client certificates.

**Acceptance criteria**
- Client creation succeeds and uses client certificates when valid `cert`, `private_key`, and (if key encrypted) `passphrase` are provided and the files exist.
- Client creation fails with an actionable error if `client_tls` is partially specified (e.g., `cert` missing, `private_key` missing).
- Client creation fails if any file path is invalid or the PEM data is malformed, or if an encrypted key is provided without a passphrase.
- Requests over TLS from the client present the client certificate to the server.
- With `allow_insecure_tls: true`, the client connects successfully to servers with self-signed or otherwise untrusted certificates.
- Existing bearer token and header behavior is unaffected.


**Title:** Decision log encoder: adaptive soft limit to maximize chunk fill rate

### Problem

The decision log uploader (`plugins/logs`) compresses events into gzip chunks and flushes a chunk whenever adding the next event would exceed the configured `reporting.upload_size_limit_bytes` (the hard limit). In practice this often produces chunks that are far below the hard limit – the encoder flushes as soon as the predicted size crosses the limit, even if the resulting compressed chunk would have been much smaller than the allowed maximum. This leads to many small uploads, wasting network round-trips and memory.

### Desired Behaviour

Introduce an adaptive *soft limit* so the encoder packs as many decisions as possible into each chunk while still respecting the hard limit.

1. **Adaptive soft-limit tuning**
   - The soft limit starts equal to the hard limit.
   - After a chunk is completed (compressed, written and closed), adjust the soft limit for subsequent chunks based on how full the final compressed chunk was relative to the hard limit:
     a. **Size < 90 % of hard limit** → the soft limit is too conservative; increase it multiplicatively for future chunks (exponential growth with a damping factor).
     b. **90 % ≤ size ≤ hard limit** → soft limit is good; keep it unchanged.
     c. **Size > hard limit** (which can happen after scaling up) → the soft limit was too aggressive; reduce it *and* re-encode the events that were in that chunk so they are repartitioned into new chunks that obey the reduced soft limit. Re-encoding must preserve all events (no loss, no duplication).

2. **Return multiple chunks per operation**
   Because scaling down may cause a single flush to produce more than one chunk, `Write` and `Flush` must return `[][]byte` (a list of chunks) instead of a single `[]byte`. All call sites (`Plugin.oneShot`, `Plugin.encodeAndBufferEvent`, etc.) need to be updated to iterate over the returned chunks and buffer them individually.

3. **Metrics**
   Expose counters (via the existing `metrics.Metrics` interface) that record how often the soft limit is **scaled up**, **scaled down**, and **remains stable**. These counters allow operators to understand how the adaptive algorithm is behaving in production.

4. **Invariants**
   - No uploaded chunk shall exceed the user-configured hard limit.
   - The total number of decision log events encoded across all chunks produced by a sequence of `Write` + `Flush` calls must equal the number of events passed in.
   - Existing tests (adapted only for the changed return types) must continue to pass; new behaviour can be validated by a separate test that exercises many similar events and checks the metrics counters and event counts.

### Notes

- The encoder lives in `plugins/logs/encoder.go` and is used from `plugins/logs/plugin.go`.
- The hard limit is currently stored in a field `limit`; the soft limit should be added alongside it and the existing gzip / buffer pipeline should not be changed beyond what the adaptive logic requires.
- The adaptive algorithm should be simple and low-latency; a small damping factor prevents the soft limit from oscillating too aggressively.

Add an adaptive limit for the upload chunk size


We need to expose the HTTP query parameters from incoming requests in the authorization input document that is evaluated against `data.system.authz.allow`. Right now the input only contains `path`, `method`, and `identity` – policy authors have no way to inspect query string parameters (e.g., `?pretty=true`, `?explain=full`, or custom parameters) when making allow/deny decisions.

**Acceptance criteria:**
1. When OPA's built-in authorization middleware receives an HTTP request that has a query string, the JSON input document supplied to the `data.system.authz.allow` rule must include a **`params`** field.
2. The `params` value must be an object whose keys are the query parameter names (decoded) and whose values are arrays of string values, following standard URL query-string parsing rules: duplicate parameter names produce multiple elements in the array, and percent-encoding is decoded.
3. If the request URL contains no query string, `params` must be an empty object (`{}`). It must never be `null` or absent.
4. The existing input fields (`path`, `method`, `identity`) must remain unchanged, with their current semantics.
5. The behavior must hold for any HTTP method (GET, POST, etc.) and regardless of whether the path contains special characters (the path itself is already split into a JSON array; query parameters should be added separately from path processing).


As an OPA user running the server, I want to enable HTTP/2 cleartext (h2c) on plain HTTP listeners so that clients can benefit from multiplexing, header compression, and other HTTP/2 performance improvements without requiring TLS.

**Acceptance Criteria**

1. **New flag** – Introduce a new `--h2c` server flag (boolean, default `false`) for `opa run -s`. When the flag is not present, the server must continue to operate exactly as before – plain HTTP endpoints only support HTTP/1.1.

2. **H2C on non-TLS HTTP listeners** – When the server is started with `--h2c`:
   - The server accepts HTTP/2 connections over the non-TLS listeners created by:
     - `--addr` (e.g., `http://0.0.0.0:8181`)
     - `--diagnostic-addr` (e.g., `http://0.0.0.0:8282`)
     - the deprecated `--insecure-addr` (HTTP only)
   - Both h2c upgrade from HTTP/1.1 and direct HTTP/2 prior knowledge connections are supported.

3. **No effect on other listeners** – TLS listeners (configured via `--addr` with HTTPS or `--tls-*` flags) and Unix domain socket listeners must not be affected by the `--h2c` flag.

4. **API responses over h2c** – Standard endpoints served on those listeners (e.g., `/health`, `/v1/data`, metrics) return correct HTTP/2 responses (status codes, protocol version `HTTP/2.0`) when accessed over h2c.

5. **Backward compatibility** – Without the flag, existing deployments see no change in protocol behavior.

server: enable H2C for HTTP listeners via CLI flag


**Implementation Request: SDK – user-provided store must take precedence over globally registered external bundle store**

## Problem
When using the Go SDK (`sdk.New`), a caller may supply a custom store via `sdk.Options.Store`. If a bundle activation plugin has also been registered globally (via `bundle.RegisterStoreFunc`), the SDK currently ignores the user-provided store and always selects the globally registered one. This discards any policies or data that the caller preloaded into the custom store, and causes queries to return unexpected results.

## Expected behaviour
- If `Options.Store` is non‑nil, that store must be used as the persistent storage backend, **even when** an external bundle store is registered.
- The globally registered external bundle store should only be used as a fallback when the caller does **not** provide an explicit store.
- The existing default behaviour (no user store → external bundle store if available → otherwise the standard in‑memory store) must remain unchanged.

## Acceptance criteria
- A user who passes `Options.Store` containing pre‑seeded policies and then queries the SDK sees the expected policy results.
- The fix must not break scenarios where only the external bundle store is available and no custom store is given.
- All existing SDK tests continue to pass.

sdk: options store takes precedence over ext store


**Title:** OPA file watcher misses policy updates on Kubernetes ConfigMap volumes (symlink mounts)

**Background**
When OPA’s `--watch` flag is enabled, it monitors policy files for changes and reloads automatically. In Kubernetes, ConfigMaps can be mounted as volumes. The kubelet implements these mounts by creating a directory of symlinks that point into a versioned “data” directory. When the ConfigMap is updated, kubelet atomically swaps the data directory and updates the symlink targets, but **file system events are generated on the parent directory, not on the symlinked files themselves**.

OPA currently watches each resolved policy file path individually. This works correctly for ordinary files but **does not react to ConfigMap updates** because the file-level events that OPA subscribes to never fire.

**Expected behavior**
OPA should detect changes to policy files regardless of whether they reside on a regular file system or are delivered via a symlink-based mount (e.g., Kubernetes ConfigMap volumes). Concretely:

1. When OPA starts with `--watch` (or equivalent configuration), it must **register watches on the unique parent directories** of all policy file paths, rather than on the individual file paths.
2. Directory-level events (create, write, remove, rename) within those watched directories must still trigger policy reload, just as file-level events did previously.
3. The set of paths that OPA loads and evaluates must not change – only the file-system notifications used to detect changes should be altered.

**Constraints & safety**
- The fix must produce a deterministic ordering. For reproducibility, the list of watched directories should be sorted (e.g., lexicographically) so that watch registration order is consistent across runs.
- The change must not break direct file watching for non-symlink deployments. Any existing test that verifies reload-on-change should continue to pass without modification of the test logic.
- The fix should not introduce duplicate watches for the same directory, even if multiple policy files share the same parent directory.

**Acceptance criteria**
- When OPA is pointed at a directory of policies that is actually a Kubernetes ConfigMap mount (symlinks to versioned data), an update to the ConfigMap triggers OPA to reload the policies within the configured ready/timeout.
- The same holds true for any directory where policy files are accessed via symlinks.
- Logging around file watch events should be sufficient to troubleshoot issues (e.g., a debug message indicating which paths are being watched).

file watching only watches parent directory


**Title:** REPL output regression: generated variables visible and references print `true`/`false` instead of values

**Description:**
Since recent internal query-rewriting changes, the OPA REPL shows wrong output for several common query patterns. Two main problems are observable:

1. **Generated variable names appear in results.** Internal variables (those automatically created by the REPL, with names containing `__repl`) are leaked into the printed bindings. The REPL must never expose such implementation details; only variables that appear in the user’s input or policy should be displayed.

2. **Single reference expressions print `true`/`false` instead of their actual value.** For a query that consists of a single reference (e.g., `data.a.b`, `p`, `servers[i]`, `q[3]`), the REPL currently prints `true` when the reference is defined and `false` when it is not. The expected behaviour is:
   - If the reference is defined, print its value (JSON or pretty-printed, depending on the current output mode).
   - If the reference is undefined, print `undefined` – not `false`.
   Other undefined queries (e.g., a rule body that produces no rows) should still print `false`.

3. **Set-reference output includes extra columns.** When querying a reference that refers to a set document or partial set rule (e.g., `p[x]` where `p` is a set), the result table sometimes includes an extra column for the whole expression. The output should show *only* the user-visible variables (e.g., `x`), matching the behaviour before the regression.

These problems affect both JSON output and the default pretty-printed output, and they must be fixed without breaking existing REPL capabilities: tracing, metrics, explain mode, wildcard suppression, the `--disable-undefined-output` flag, infix operators (like `p[x] > 1`), `with` modifiers, multi-expression bodies, and pretty-print width limits.

**Acceptance Criteria**

- In all queries, no generated/internal variable names appear in the printed results.
- A single reference query prints the reference’s value when defined.
  Example: after loading `data.a = [7,8,9]`, the query `data.a` prints `[7,8,9]`.
- A single reference query that is undefined prints `undefined` (unless undefined output is disabled).
- A set-reference query such as `p[x]` (for a set `p`) prints only the bindings for `x`, with no extra column for the reference itself.
- Other undefined queries (e.g., a rule body that yields no results) still print `false`.
- All existing REPL features (tracing, metrics, explain, output formatting options, etc.) continue to work as before.


```
Bug: Bundle downloader loses ETag when server omits ETag in 304 response

When OPA periodically fetches bundles from a remote server, it uses
the standard If-None-Match / ETag mechanism to avoid re-downloading
unchanged bundles. If the server responds with 304 Not Modified,
OPA should preserve the previously known ETag so that subsequent
requests continue to include If-None-Match and continue to receive
304s while the bundle is unchanged.

Some HTTP servers (e.g., Nginx, Azure Blob Storage, and the Rego
playground’s nginx-based serving) do not include the ETag header in
304 responses, which is allowed (though not recommended) by the
HTTP specification. When OPA's bundle downloader receives a 304
without an ETag header, it appears to discard the previously known
ETag. As a result, on the next polling cycle OPA sends a request
without an If-None-Match header, receives the full bundle again,
and the caching loop resets—effectively re-downloading the bundle
on every poll, wasting bandwidth and processing.

Expected behaviour: if a 304 response does not contain an ETag
header, the downloader should continue to use the last known ETag
for future requests, preserving the caching cycle exactly as if
the server had echoed the ETag.

This affects any deployment where the bundle server omits the ETag
on 304s, causing OPA to repeatedly fetch the full bundle despite no
actual content change.
```

Do not rely on ETag in 304 server response headers (#3362)


**Title:** Cover tracer should signal it does not need local variable metadata

**Background:**
The OPA evaluator can skip the work of plugging local variable metadata into trace events when a tracer indicates it does not require that metadata. The `Profiler` tracer already leverages this optimization by returning a configuration that disables local variable plugging. The `Cover` tracer currently does **not** provide such a signal, so evaluations with coverage tracing enabled always pay the cost of plugging local variables even though the coverage report never uses them.

**Request:**
Implement the same optimization for the `Cover` tracer. Add the necessary configuration so that the evaluator knows coverage tracing does not need local variable metadata. This should notably improve evaluation speed when coverage tracing is active, especially for policies that contain many or large variables.

**Constraints & guidance:**
- Mirror the approach already used by the `Profiler` tracer.
- The change must remain compatible with the existing `Tracer` interface.
- No behavioural changes to coverage reports or other tracers.

cover: Disable local var plugging on trace


```
Fix: service clients using unconfigured global logger, causing missing logs.

In OPA server runs, service client logs (e.g., from bundle/download clients) are
often missing even when `--log-level debug` is passed. The root cause is that
the plugins.Manager creates service clients before the manager logger is
initialized; those clients therefore fall back to the global logger, which never
has the configured log level set.

Also, as a safety net, the global logger should be configured with the supplied
log level and format, even though it should not normally be used after the fix.

Additionally:
- Remove the deprecated `NewStandardLogger` function; existing callers (including
  in tests) should use `New` instead.
- Extract log-level parsing out of `runtime.NewRuntime` into a reusable helper to
  avoid duplicating the level strings and error handling.
```

plugins: Fix logger initialization on plugin manager


**Title:** Partial evaluation does not inline negated expressions into support rules

**Description:**
When OPA’s partial evaluator encounters a negated expression (e.g., `not q`), it currently preserves the negation as-is in the residual query without attempting to inline its complement. This limits the effectiveness of partial evaluation because:

- The constraints inside the negated goal are not extracted into a dedicated support rule.
- The residual query still directly references the original policy rule, which prevents rule indexing and other optimizations from being applied to the negated sub-goal.
- If the complemented expression is always undefined (i.e., can never be satisfied), the evaluator misses the opportunity to drop the negation entirely.

**Expected behavior:**
During partial evaluation, each negated expression should be processed so that its complement is evaluated and the result is saved as a new support rule. The original negated expression in the residual query should then be replaced by a reference to that support rule. If the complemented expression cannot be satisfied under any bindings, the negated expression is trivially true and should be omitted from the residual query altogether.

**Example (behavioral sketch):**
For a policy like:
```
p { input.x = 1; not q }
q { input.y = 2 }
```
Querying `data.test.p = true` under partial evaluation should produce a residual that refers to a generated support rule (e.g., `data.partial.__not_...`) rather than `not data.test.q`, and that support rule should contain the condition `input.y = 2`. If another negated goal `not r` is present and `r` has no satisfiable body (e.g., `r { false }`), that negation should be completely removed.

**Impact:**
Negation appears in many real-world policies (e.g., Kubernetes admission control, API authorization) and without this inlining the partial evaluator produces suboptimal policy representations, reducing the applicability of downstream indexing and performance improvements.

Generate support rules for negated expressions


```
Title: Rule "body" field serializes as null when empty instead of []
Type: Bug Report
Severity: Medium

Description:
When a Rule has no body expressions (e.g., a default rule or a rule that unconditionally returns a value), the JSON serialization of the rule's `body` field is `null`. Clients consuming AST JSON (e.g., via the REST API, policy bundles, or serialized modules) expect the body to always be an array of expressions. Receiving `null` causes errors in downstream tools that attempt to iterate over the body field.

Steps to Reproduce:
1. Create a simple policy with a rule that has no body: `allow = true`.
2. Retrieve the module's JSON representation (e.g., via `GET /v1/policies/<id>` or by dumping the parsed module).
3. Observe the `"body"` field in the rule's JSON object.

Actual Behavior:
The `"body"` field is `null`.

Expected Behavior:
The `"body"` field should be an empty JSON array `[]`.

Additionally, a rule with an explicitly empty body (i.e., `Body{}`) should also serialize to `[]`. The invariant should be: the body field is always a JSON array, never `null`, regardless of whether the underlying Go slice is nil or empty.

Impact:
Consumers that deserialize OPA's AST JSON and then iterate over or append to the body field will encounter nil pointer panics or unexpected type mismatches. This issue forces every consumer to guard against `null` body values, which is brittle and contradicts the documented schema.
```

Update ast.Body to serialize empty values to []


Subject: Decision logs leak X-AMZ-SECURITY-TOKEN header despite Authorization masking

When the REST plugin is configured with debug logging enabled, outgoing request headers are included in the log output. The `Authorization` header value is correctly replaced with `REDACTED` to avoid exposing bearer tokens or other credentials. However, the `X-AMZ-SECURITY-TOKEN` header (commonly used for AWS STS temporary credentials) is written in plain text, leaking a sensitive session token into the logs.

Steps to reproduce:
1. Use an AWS signing or credential plugin that attaches an `X-AMZ-SECURITY-TOKEN` header to requests.
2. Enable debug logging on the REST client.
3. Observe that the log entry contains the raw `X-AMZ-SECURITY-TOKEN` value.

Expected behavior:
The `X-AMZ-SECURITY-TOKEN` header should be masked in the same way as the `Authorization` header—its value replaced with `REDACTED`.

Additional context:
This is a counterpart to the existing `Authorization` header redaction. The list of headers that require masking should be easy to maintain so that other sensitive headers can be added without duplicate logic. At minimum, `X-AMZ-SECURITY-TOKEN` must be covered today.

plugins/rest: masks X-AMZ-SECURITY-TOKEN header in decision logs (#6423)


Fix the file watcher in server mode panicking when policies exist in storage but not on disk at reload time.

When file watching is enabled and a file-system event triggers a reload, the server panics if:
- A policy file is removed after the FS notification but before `loader.All` scans the directory.
- A policy is added via the HTTP API while file watching is active.

The panic occurs when the resulting set of policies would conflict during compilation (e.g., a type error), because the reload logic only considers modules loaded from disk, omitting policies already present in the store.

The reload must:
- Include all policies from the store, not just those returned by `loader.All`.
- Correctly delete policies that were removed from disk (accounting for the race between notification and loading).
- Still produce a compilation error (not a panic) when conflicts exist, letting the existing error reporting surface the issue.

Fix file watch bug causing panic in server mode


```
Title: Type checker: improve handling of reference-resolved argument types and partial-object comparison

Problem 1 – Builtin argument type checking with references

When a variable is assigned a concrete type and is then used as an element of a composite argument to a builtin, the type checker does not detect the type mismatch. For example:

    a := "foo"
    s := sum([1, a])   # should report "sum: invalid argument(s)"

Currently this compiles without error. The case `sum([1, "foo"])` (literal) is already caught, so the discrepancy is that the type checker does not retrieve the fully-resolved type when an argument is a reference. This also applies inside rule bodies.

The desired behaviour is that when a builtin argument has a known type that is not compatible with the builtin’s declared parameter type, an "invalid argument(s)" error is raised before any inference is attempted. However, when the argument type is still unknown (e.g., an untyped variable coming from `input`), no such early error should be raised; inference should proceed as normal.

Problem 2 – Comparing partial objects (objects with dynamic properties)

Rules that define an object incrementally (e.g., `obj["key"] := value`) produce objects with dynamic properties. The type checker currently reports false unification errors when comparing two such partial objects, or when comparing a partial object with a literal object where some keys are conditionally defined.

Examples:

    # both objects are partial → should compile, currently errors
    obj1["a"] := input.a
    obj1["b"] := input.b
    obj2["x"] := input.x
    obj2["y"] := input.y
    test { obj1 == obj2 }

    # partial object compared to a literal where a dynamic key is missing → should compile
    obj["a"] := true
    obj["b"] := "foo" if { input.foo == "bar" }
    test { obj == {"a": true} with input as {"foo": "baz"} }

The root cause is that the object compatibility check used during unification treats all keys of the left-hand object as required in the right-hand object. For objects with dynamic properties, missing keys should be allowed because they can be supplied by the dynamic portion. This is already handled correctly in the inference path (the `unify1*` functions); the fix should align the static compatibility check with that behaviour.

Expected behavior

1. Builtin argument validation must use the most specific known type for each argument. If that type is incompatible with the formal parameter type, an argument error is emitted. This must not block inference when the type is still untyped (i.e., nil type).

2. When checking compatibility of two object types where the left-hand object has a dynamic value type, a key present in the left object but absent in the right object should not be considered a mismatch. The check should fall through to the dynamic-property handling already present in the surrounding unification logic.

Notes

- These fixes should not introduce regressions in existing compile-error tests.
- The object compatibility change must not weaken checks for purely static objects (without dynamic properties) – there, every key remains required.
- No new public API or configuration surface is needed; all changes are internal to the type checker.
```

type checker: identify compatible type from reference (#8485)


**Title:** `opa test --explain` should automatically enable verbose output

**Description**

When a user passes `--explain` to `opa test` to get failure explanations, they still need to also supply `--verbose` (or `-v`) to see the output. If `--explain` is set but `--verbose` is omitted, the explanation output is suppressed, making the flag appear to have no effect.

We should change the behaviour so that explicitly providing `--explain` implies `--verbose`. That way `opa test --explain=fails` (or any other valid explain mode) works as a single flag and produces the expected detailed output without requiring a second flag.

**Acceptance behaviour**

- Running `opa test --explain=<mode>` (where `<mode>` is any supported value) must produce verbose test output.
- The explicit `--verbose` flag should remain available and still work; when both are given the result should be the same.
- The change must not affect commands where `--explain` is not supplied (i.e., default test behaviour stays the same).

**Additional context**

This is a usability improvement reported in #2069. The key invariant is: if the user explicitly sets an explain mode, they intend to see the explanation; therefore verbose output should be enabled automatically.

cmd/test: --explain now turns on verbose output


The WASM runtime's `regex.match`, `regex.is_valid`, `regex.find_all_string_submatch_n`, and `glob.match` built-ins treat `opa_string_t` values as null-terminated C strings, ignoring the explicit `len` field. When a subsequent evaluation supplies a string that is shorter than a previous one, these functions may read stale characters from memory beyond the intended string boundary. For example, after evaluating `regex.match("^world$", "no-world")`, a following evaluation with `regex.match("^world$", "world")` can fail because the match is performed against `"worldrld"` (leftover from `"no-world"`). Fix the functions to construct `std::string` objects using the pointer and length from `opa_string_t` before compiling patterns or matching against values. Ensure the pattern caching key uses the length-bounded string so that compilation correctly identifies distinct patterns.

wasm/regex.{match,is_valid,fiind_all_string_submatch_n}: fix fixed-length string casts


# Partial evaluation: repeated rule references with comprehensions yield incorrect namespaced output

When a query references the same rule more than once and that rule’s value is a comprehension built over an unknown (e.g., `input`), partial evaluation produces incorrect results for the second and subsequent references.

## Steps to reproduce

Given a module:

```rego
package test

p = c {
    a = input
    c = [1 | b = a[0]]
}
```

And a partial-evaluation query that references `data.test.p` twice:

```
data.test.p = x; data.test.p = y
```

## Expected behavior

Both `x` and `y` should be bound to independently-namespaced comprehensions. For example, the output should contain two distinct variables for `b` (e.g., `b1` and `b2`), each scoped to its own comprehension occurrence.

## Actual behavior

The second binding shares or loses the namespaced variable that was produced during the first binding's evaluation. The partial-evaluation result is effectively corrupted for the second reference—`y` ends up with a comprehension that reuses the variable introduced for `x` instead of getting a fresh one.

## Likely cause

During partial evaluation of a comprehension with unknown terms, the evaluator namespaces the variables inside the comprehension before saving it into the residual query. It appears that this namespacing mutates the comprehension term in-place within the original query AST. When the same rule (and therefore the same comprehension expression) is encountered again during evaluation, the already-namespaced (or partially-namespaced) AST node is reused, and the second evaluation either skips re-namespacing or produces a mangled result.

## Fix direction

The comprehension term should be treated as immutable during partial evaluation. Before performing any namespacing or mutation on the comprehension, the evaluator should operate on a deep copy so that the original AST is left intact for subsequent evaluations within the same query.

## Impact

Any partial-evaluation query that references a rule containing a comprehension with unknowns more than once is affected. This includes common patterns where the same policy decision is needed in multiple places within a single partial-evaluation request.

topdown: Save comprehensions containing unknown terms


**Title:** Cap the size of compiled regex and glob pattern caches to prevent unbounded memory growth

**Background**
OPA maintains internal caches of compiled regex patterns (used by `regex.match` and related built‑ins) and compiled glob patterns (used by `glob.match`). These caches have no upper limit today, so every distinct pattern encountered during evaluation is retained forever. When a policy processes patterns derived from large input documents or generates many unique patterns dynamically (e.g. in a loop over input data), the caches can grow without bound, leading to memory exhaustion and degraded performance.

**Request**
Place a hard cap on the maximum number of entries that can reside in the regex cache and in the glob cache simultaneously. Once a cache reaches its capacity, the insertion of a new pattern must evict an existing entry before the new one is stored. The exact entry to evict is not important—any single entry can be removed (for example, the first one returned by iterating the cache). The cap value should be a small constant (e.g., 100 entries) and does not need to be user‑configurable.

**Scope**
The change must apply to **both** cache implementations that ship with OPA:

1. The **Go topdown evaluator** – the caching logic that backs calls to regex and glob built‑in functions.
2. The **WebAssembly runtime (C++)** – the corresponding independent caches used by `opa_regex_match` and `opa_glob_match`.

**Thread‑safety constraint**
Cache access is already protected by synchronization (mutexes). The eviction must be performed under the same critical section so that concurrent callers observe a bounded cache without races.

**Expected behavior**
- After the cache reaches the cap, its size stays at or immediately below the cap.
- All existing regex/glob matching semantics remain unchanged.
- No persistent leak occurs when many unique patterns are evaluated over the lifetime of a policy evaluation.

**Non‑goals**
- A sophisticated eviction policy (e.g., LRU). A simple “evict any one entry” is acceptable.
- User‑visible configuration of the cap size.

topdown: Adding cap to caches for regex and glob built-in functions (#6846)


When the type checker encounters a plain reference to a function (without a call), it currently emits a generic “unsupported ref” error. Improve this to produce a clear diagnostic:

- The error must say something like: “function <ref> used as reference, not called”.
- The <ref> should be the fully-qualified function name (e.g. `data.play.f`, `data.base.foo`), for both locally-defined and imported/data functions.
- Any compiler-generated local-variable suffixes (such as `__local1__`) that happen to be appended to the reference should be stripped so the message points at the function name, not an internal variable.
- Only trigger this specialised message when the type of the referenced node is a function type; all other “unsupported ref” cases should keep their existing messages.
- The fix must work in contexts like `some x in f` and `[data.base.foo]` where a function appears in a non-call position.

ast: Improve type error message when referencing functions  (#8253)


**Feature Request: `net.cidr_expand` built-in with cancellation support**

**Problem**
There is currently no way in Rego to enumerate all individual host addresses within a CIDR block. Existing `net.cidr_*` built-ins only check containment or overlap; they cannot produce the addresses themselves. Some policy use cases require iterating over every address in a range—for example, to validate hosts against an external API or to enforce per-IP rules where only a CIDR is given.

**Desired behavior**
A new built-in, `net.cidr_expand(cidr)`, should accept a CIDR string (IPv4 or IPv6) and return a *set* of all host addresses in that network, *including* the network address and broadcast address (i.e., the entire range). The function must handle both small and large ranges without hanging indefinitely.

**Critical safety requirements**
- **Cancellation**: Expanding a large CIDR (e.g., `/1`) can generate billions of addresses. The built-in must be cancelable so that OPA can abort it if a timeout or cancellation signal is triggered (matching the existing OPA evaluation cancellation mechanism). If canceled, it should return a clear, descriptive error (e.g., `eval_cancel_error` with a message indicating the built-in was interrupted).
- **Error handling**: Invalid CIDR inputs (e.g., mask `/33`) should produce a standard error (like `invalid CIDR address`), consistent with how other `net.cidr_*` functions handle malformed inputs.
- **Output type**: The return value must be a set of strings, each string being the textual representation of an IP address.

**Context**
The `net` built-in family already includes `net.cidr_contains`, `net.cidr_intersects`, and `net.cidr_overlap`. A `net.cidr_expand` would naturally complete this set. This feature is needed when an external API does not accept CIDRs, or when a policy must produce an explicit allow/deny list from a CIDR source.

Please ensure the new built-in is registered in the same way as the existing `net.cidr_*` functions and that it works correctly with the OPA query evaluation cancellation infrastructure.

topdown: Add net.cidr_expand built-in function


Title: Remove hidden dependency on system `diff` command in `opa fmt --diff`

Description:
The `opa fmt` command with the `--diff` flag currently shells out to the system's `diff` utility to produce a diff between the original and formatted Rego. This makes the command non‑portable — it fails on systems where `diff` is not installed or not in the PATH. The repository already vendors `github.com/sergi/go-diff/diffmatchpatch`, which can be used to generate diffs entirely in‑process, eliminating the hidden dependency.

Acceptance criteria:
- When `opa fmt --diff` is run, the diff between the original and formatted content is generated using the vendored `go-diff` library (no external process).
- The diff is printed to the output destination (e.g., stdout) as a human‑readable text.
- If the output writer fails (e.g., a broken pipe or full disk), the `fmt` operation returns an error (not silently ignore).
- The command no longer writes any error messages to stderr originating from the diff step; any diff‑related errors are propagated through the normal error return.
- The previous temporary file creation and cleanup for diffing is removed.
- The behavior when no formatting changes are detected remains unchanged (no diff output, no error).
- The existing `--fail` flag behavior is preserved: if `--diff` shows a diff and `--fail` is set, the command returns an error indicating an unexpected diff.

Edge cases / constraints:
- The new diff implementation must not crash on inputs that are valid UTF-8 (the library already handles this).
- The output format may differ from the previous unified‑diff output (the library’s “pretty text” format is acceptable), as long as it is still human‑readable and conveys the changes.
- The change must not introduce any new dependencies beyond what is already vendored.

cmd: removes hidden dependency on diff command (#6452)


Rework the decision log plugin’s shutdown flushing to eliminate flakiness.
Currently `Stop` computes a graceful shutdown period from the context deadline, creates a sub-context with that timeout, and passes a cancel function to `flushDecisions`. `flushDecisions` calls `cancel` to signal completion, which races with the deadline and causes unreliable behavior.

Instead, stop deriving any new timeouts: rely only on the deadline of the context that `Stop` receives. Pass that context unchanged to `flushDecisions`. Use an explicit done channel to signal that all buffered decisions were successfully uploaded. When the context expires (deadline exceeded or canceled), always log a single message that the plugin stopped with decisions possibly still in buffer; do not log differently for each case. The surrounding logic that skips flushing when no deadline is set (or when no service is configured) must remain unchanged.

This change should make shutdown deterministic: a forced shutdown with a near‑zero deadline must leave buffered logs in the plugin, rather than attempting to time the exact stop duration.

plugins/logs: Refactor shutdown log flushing


```
Add DELETE support for /v1/data resources

The v1 data API (GET, PUT, PATCH, POST) currently returns 405 for DELETE
requests. We need the ability to remove data documents and entire
subtrees.

Requirements

- Accept DELETE at /v1/data/{path} (where {path} is a document path like
  "a/b/c").
- If the path does not exist, return 404.
- If the path exists, remove the document and all sub-documents.
  Return 204 No Content on success.
- After a successful DELETE, a subsequent GET on the deleted path or any
  sub-path must return 200 with an empty JSON object body {} (no "result"
  wrapper). This matches the existing contract that undefined documents
  are returned as an empty object.
- GET requests to ancestor paths must reflect the removal; for example,
  if the only child of /data/a was /data/a/b and it gets deleted,
  GET /data/a should return {"result": {}}.

Non-functional constraints

- The new endpoint must be registered on the mux.Router alongside the
  other v1 data methods and include Prometheus instrumentation.
- The handler should follow the same transactional pattern as the v1 data
  PUT handler: parse the path, open a write transaction, perform the
  removal via the store, commit, and handle errors by aborting the
  transaction before returning an error response.
- No new external dependencies.
```


As a maintainer, write a clear backlog ticket for this feature request. The ticket should describe the missing capability and the expected behavior, including any safety requirements and known edge cases, without prescribing exact implementation details or referencing tests.

plugins, runtime: Add visibility for server init


Add a `fails` explanation mode in the REPL that filters a top‑down trace to show only failure‑related events.

A new `fails` command should toggle this mode on and off, exactly like the existing `trace` and `notes` toggles. When `fails` is active the output must be filtered through `lineage.Fails` from `github.com/open-policy-agent/opa/topdown/lineage` (the `notes` mode should also move to `lineage.Notes`; remove the old `topdown/notes` import).

Internally, the current explanation‑mode tracking uses an `int` and a debug state struct (`replDebug`) with separate boolean fields per mode. This is awkward to extend. Refactor `explainMode` to a `string` type with values `"off"`, `"full"`, `"notes"`, `"fails"`, replace `replDebug` with a struct that carries a single `Explain` field (plus the existing `Metrics`, `Instrument`, `Profile` booleans), and update the `show debug` JSON output to emit the new field instead of the old separate booleans. The `trace` and `notes` toggle commands should be consolidated into a single helper that accepts a mode argument to avoid duplication.

The raw‑trace, notes, and fails branches must be handled in both `evalBody` and `evalPartial`. Add the `fails` command to the built‑in command table so it appears in help. The `show debug` output format will change, but that surface is not considered a stable API. No new tests are required.

repl: Update REPL to support fails explanation mode


Allow test rules to be marked as skipped by giving them a `todo_test_` name prefix.

- For rules starting with `todo_test_`, the test runner must recognize them as tests (matching the same namespace/filter logic as `test_` rules) but must **not** execute them.
- Such rules must be reported as **SKIPPED** in the `opa test` output.
  - The pretty reporter summary should include a `SKIPPED: <n>/<total>` line.
  - The JSON reporter should include `"skip": true` in the result object.
- Skipped tests must not be counted as passed, failed, or errored.
  - `Pass()` must return `false` for them.
  - `String()` should produce `SKIPPED` (without a duration).
- Update the CLI help text for `opa test` and the policy-testing documentation to describe the `todo_test_` prefix.


**Title**: Scoped bundle activation should validate cross‑scope references and avoid blocking queries with redundant re‑compilation

**Background**
When OPA is configured with the bundle plugin and a bundle declares a `roots` (scoped bundle), activating that bundle replaces the policies under those roots. Politices outside the bundle’s scope may still reference rules that reside under those roots.

**Observed problems**

1. **Missing cross‑scope validation** – If a scoped bundle is activated and, as a result, a rule that a remaining (out‑of‑scope) policy depends upon is removed or changed, the system does not detect this inconsistency at activation time. Instead, the error surfaces later (runtime) when a query triggers evaluation of the now‑broken dependency. This can lead to policy decision failures or incorrect decisions.
2. **Trigger‑callback latency** – After every bundle‑driven policy change, the manager’s commit callback re‑parses and re‑compiles **all** policies from storage while holding a lock that blocks policy queries. The compilation was already performed during bundle activation, so this second compilation adds unnecessary latency to queries that are waiting for the lock.

**Desired behaviour**

- **During bundle activation** the system must compile the incoming bundle modules **together with** any policies that are not affected by the bundle’s roots (i.e., the “remaining” modules in storage). If the resulting compilation fails for any reason—e.g., missing dependencies, type conflicts, safety violations—the activation must be aborted and the system must continue to use the previously active bundle and compiler state.
- **After successful activation** the compiler produced by that activation step must be reused in the manager’s commit callback, so that policy queries do not wait for a second, redundant compilation pass.
- The fix must not introduce any additional locking or serialisation overhead beyond what the current storage transaction already provides.

**Acceptance criteria**

1. **Scoped bundle with cross‑scope dependency**:
   - Add a policy outside the bundle’s roots that references a function or rule defined under the bundle’s roots.
   - Activate a new revision of the scoped bundle that removes the referenced definition.
   - The activation must **fail**, the active bundle revision must remain at the previous value, and query evaluation against the unchanged state must continue to succeed.

2. **No re‑compilation in commit callback**:
   - Register a storage trigger that records the `TriggerEvent` context.
   - Activate a bundle.
   - Assert that the second `TriggerEvent` (the policy change event) carries a pre‑built compiler object on its context (i.e., the system does not fall back to loading and compiling from storage again).

3. **Baseline scoped‑bundle behaviour unchanged**:
   - Activating a valid scoped bundle that only affects its own roots must succeed and produce correct policy evaluation results immediately after activation, without perf regressions.

bundle: Cache compiler on storage context


The `--h2c` flag currently only enables HTTP/2 cleartext on TCP listeners. When OPA is started with UDS addresses (e.g., `unix:///path/to/socket`), the flag is quietly ignored and the listener only accepts HTTP/1.1. Make `--h2c` work for UDS listeners as well: an HTTP/2-capable client connecting over the socket should be able to negotiate h2c, and the server should respond with HTTP/2.

**Observable behavior**
- When `--h2c` is enabled, a UDS listener must accept HTTP/2 cleartext connections (in addition to HTTP/1.1).
- When `--h2c` is disabled, UDS behavior must remain as before: only HTTP/1.1 is supported, and HTTP/2 negotiations should fail or fall back to HTTP/1.1.
- The existing TCP h2c behavior must not regress.

**Safety/edge cases**
- Both HTTP/1.1 and HTTP/2 should work on the same UDS when h2c is enabled (h2c upgrade mechanism or prior knowledge).
- The change must not affect TLS listeners or other listener types.
- Ensure that the socket cleanup logic (unlink before binding) is preserved regardless of h2c status.

Keep the fix minimal; use the same mechanism already used for TCP h2c support.

run: support `--h2c` with unix domain socket (UDS)


**Title:** SDK does not allow setting a custom instance ID for decision logging

**Summary**
Every call to `sdk.New(ctx, opts)` generates a fresh random UUID as the OPA instance ID. There is no way to supply a fixed, user-chosen identifier via `sdk.Options`. This makes it impossible to correlate decision logs from the same logical OPA instance across restarts or between separate processes that should share a logical identity.

**Observed behavior**
- Constructing an OPA instance with `sdk.New` always creates a new UUID internally.
- The resulting decision‑log entries contain that UUID in `labels.id`.
- No field in `sdk.Options` allows the caller to influence the instance ID.

**Expected behavior**
- Add an optional `ID` field to `sdk.Options`.
- When the field is a non‑empty string, `sdk.New` must use that value as the OPA instance ID instead of generating a UUID.
- When the field is empty or unset, the existing UUID‑generation behaviour must be preserved (backward compatibility).
- Any decision logs emitted by the instance (e.g., via `console` decision logging) must use the caller‑provided ID in the `labels.id` field, exactly as they would for an auto‑generated UUID.

**Impact**
Without this capability, organizations that embed the OPA SDK and rely on decision logs for auditing or monitoring cannot maintain a stable instance identifier, hindering log correlation and traceability.


```
Optimize the built-in functions `json.remove` and `json.filter` to reduce memory allocations and copy overhead when processing large JSON structures.

The core issue is that the current implementations grow result collections incrementally (e.g., repeated `Append` calls, default‑sized containers) without using capacity hints. This causes unnecessary re‑allocations. For large inputs—objects with many keys, arrays with many elements—this leads to excessive GC pressure.

Acceptance criteria:
- When building result objects/sets/arrays, pre‑allocate based on the size of the source value (the source size is a safe upper bound).
- In the array‑handling path of `json.remove`, accumulate elements in a pre‑sized Go slice and construct the final AST array once, rather than appending element‑by‑element to a growing AST array.
- The intermediate filter structure built inside `json.filter` (from the supplied paths) should also be pre‑allocated to avoid rehashing and copying.
- The `Array` case in the lower‑level filtering logic (used by `json.filter`) should likewise avoid incremental `Append` allocations.

Functional behavior must not change; this is a pure performance optimization with identical results.
```

perf(topdown): optimize json.remove and .filter (#8193)


When a bundle of policy modules is loaded into the in-memory store, the write transaction triggers on‑commit callbacks while the store holds the read‑lock, so policy queries are blocked for the entire duration. For large bundles, parsing and compiling all modules inside the trigger can take seconds, causing unacceptable query latency spikes.

We want to defer that expensive work, but the trigger callback has no way to know that the current transaction is a bundle load. The callback currently receives only policy/data events with no caller‑supplied metadata.

**Expected behavior**
Callers should be able to pass opaque, arbitrary key‑value metadata when creating a write transaction, and that metadata should be carried through to the `TriggerEvent` delivered to every on‑commit callback. This would let callers tag transactions (e.g., `{"source": "bundle"}`) and allow triggers to decide whether to perform work synchronously or skip it.

**Acceptance criteria**

- `storage.TransactionParams` gains a way to attach a set of key‑value pairs (a simple context container) when calling `NewTransaction`.
- The container is nil‑safe (accepting nil without panicking) and supports setting and retrieving arbitrary keys.
- When a transaction commits, the metadata is populated onto the `storage.TriggerEvent` that is passed to the `OnCommit` callback of any registered `storage.Trigger`.
- Existing code that does not provide a context should see no change in behavior; the context in the trigger event should be nil or empty when not set.

**Impact**
Without this feature, every bundle load forces a full parse/compile pass while holding the store’s read‑lock, directly impacting query latency and availability.

storage: Add context parameter to transactions and triggers


**Issue: `opa inspect --annotations` no longer includes annotation locations**

### Context
The `opa inspect` command supports a `--annotations` (or `-a`) flag that enriches the output JSON with metadata annotations extracted from Rego files. Historically, each annotation entry included a `location` field that described the file, row, and column where the annotation applied. A recent change has caused these location fields to disappear from the output.

### Acceptance criteria
1. **CLI behaviour** – When `opa inspect -a <bundle.tar.gz|directory>` is executed, every object in the `annotations` array must contain a `location` field with non-zero `file`, `row`, and `col` values that accurately reflect the source position of the annotated statement (package, rule, etc.).
2. **Go API parity** – The `inspect.File(path, includeAnnotations)` function should behave consistently with the CLI. When `includeAnnotations` is `true`, the `AnnotationsRef` values returned must carry populated location information. When `includeAnnotations` is `false`, location data for annotations may be omitted (consistent with the general principle that location inclusion is tied to the annotations flag).
3. **No regression for other output** – The presence of location data on other parts of the inspect output (e.g., package paths) must not break. The change should only restore the missing annotation locations without altering unrelated fields.

### Notes / constraints
- The regression is likely due to the inspection pipeline losing the `JSONOptions` that control whether the parser attaches location data to annotation AST nodes. The fix should ensure that when bundle loading is performed for inspection purposes, the necessary options are threaded through the loader and bundle reader so that parsed modules carry annotation locations.
- The implementation should be compatible with the existing `ast.ParserOptions` and `ast.JSONOptions` mechanisms. Do not introduce a parallel mechanism; reuse the standard `IncludeLocation.AnnotationsRef` toggle.
- The fix should not affect bundle loading for non-inspection purposes. Other callers of the bundle reader and file loader should continue working without changes.

[ast/inspect] Fix issue with unset annotation data


```
## Title: Discovery bundle must not overwrite the discovery service configuration

### Problem
When OPA is configured to use discovery, the discovery bundle periodically delivers new configuration. If that bundle includes a service entry with the same name as the service that fetches the discovery bundle, OPA currently applies the new configuration, potentially breaking future discovery (e.g., if the URL or credentials change). This leaves OPA in a state where it can no longer retrieve further updates.

### Desired behavior
The configuration of the service used to fetch the discovery bundle (the “discovery service”) must be protected from changes coming from the discovery bundle. If the bundle attempts to change that service’s configuration to something different from the current known configuration, OPA should:

- Reject the discovery bundle update entirely.
- Log a clear error message indicating that updates to the discovery service are not allowed.

If the service entry in the bundle is identical to the current configuration, no error should be raised and the update should proceed normally. All other services in the bundle’s `services` section may still be added, removed, or modified as before.

### Acceptance criteria
1. When the discovery bundle’s `services` section includes an entry for the discovery service name **and** the configuration differs from the existing configuration, OPA must reject the update and log an error.
2. When the entry matches exactly the current configuration, the update must succeed without error.
3. Adding, removing, or modifying entries for services **other than** the discovery service continues to work as before.
4. Bundles that do not include the discovery service entry are unaffected.
```

plugins: Additive updates to services when discovery enabled


Title: Non-deterministic Wasm compilation output with multiple entrypoints

When compiling policies to the `wasm` target with several entrypoints, the resulting bundle’s manifest `.manifest` `WasmResolvers` order and the import statements that get injected into the remaining Rego modules can vary across builds. This instability comes from deduplicating imports per package using an unordered map, which makes iteration order non-deterministic.

**Desired behavior**
- The `WasmResolvers` entries in the manifest should appear in the same order as the entrypoints were specified.
- The import statements injected into modules after removal of entrypoint rules should follow a consistent, predictable ordering (for example, preserving the order of first occurrence across entrypoints) while still being deduplicated per package.

**Constraints**
- Duplicate imports for the same package must still be collapsed (only one copy injected), but the selection must be deterministic.
- The behavior for the `rego` target is unchanged; only the wasm compilation path is affected.
- The fix should not alter the logical meaning of the compiled bundle, only the order of certain elements in the output.

This task belongs in the `compile` package, specifically the code path that handles entrypoint pruning and manifest construction for the `wasm` target. Please make the output deterministic across repeated compilations with identical inputs.

compile: Deterministic wasm entrypoint imports


```
Title: Decision log `path` is corrupted for queries containing leading characters from the set "data."

The decision log plugin derives the `path` field from a Rego query by removing the "data." prefix and converting the remaining dots to slashes.
However, the prefix removal step currently behaves as if it uses a *character cutset* (`"data."`) instead of a literal prefix. As a result, for some queries the leading characters after the prefix are incorrectly stripped when they are members of the cutset, leading to wrong `path` values.

### Steps to reproduce
1. Evaluate a query whose string starts with “data.” but contains leading characters from the set `{d,a,t,.}` after the prefix.
   Example: `data.tda.bar`
2. Observe the `path` field in the decision log event.
   Actual: `"bar"`
   Expected: `"tda/bar"`

Similar corruption occurs for:
- `data.datum.x` → actual `"um/x"` (expected `"datum/x"`)
- `data.admin` → actual `"in"` (expected `"admin"`)

### Expected behavior
The conversion should:
- Remove exactly the literal prefix `"data."` once from the beginning of the query, and only if it is present.
- Replace all remaining `.` characters with `/` to form the `path`.
- Never remove additional characters that merely appear in the prefix string.

### Impact
The `path` field in decision log events no longer reliably reflects the original query, breaking downstream consumers that rely on it for audit, monitoring, or replication. This is a regression in the decision logger’s query-to-path conversion.

### Context
This was reported as issue #783.
```

Fix query to path conversion in decision logger


```
### Description
The Rego built-in `units.parse_bytes` does not accept floating-point quantities in the input string. For example, `units.parse_bytes("1.5KB")` currently returns an error: `could not parse byte amount to integer`. The same applies to values like `0.5KB`, `.5KB`, `1.1KiB`, etc.

### Expected Behaviour
`units.parse_bytes` should parse the quantity as a float and multiply by the appropriate unit factor (e.g., 1000 for KB, 1024 for KiB). Because a byte count must be an integer, the result should be rounded down to the nearest whole byte when the calculation yields a fractional value. For instance:

- `units.parse_bytes("1.5KB")` → 1500
- `units.parse_bytes("1.1KiB")` → 1126 (since 1.1 * 1024 = 1126.4, round down)
- `units.parse_bytes(".5KB")` → 500

The rounding should be towards zero (floor) for positive quantities.

### Actual Behaviour
The function returns an error stating the byte amount could not be parsed to an integer.

### Impact
This makes it impossible to use `parse_bytes` for memory or disk specifications that include decimal fractions, a common human-readable format (e.g., `"1.5Gi"` for resource limits in Kubernetes). Users currently must perform manual conversion or avoid using `parse_bytes` in these scenarios.
```

topdown: Let units.parse_bytes support float values (#3299)


We need a fix for the `Timer` implementation in `v1/metrics`.
Currently, calling `Stop()` more than once without an intervening `Start()` adds extra time to the accumulated value. The second `Stop()` incorrectly measures from the original start time, making the total far too large. This violates the expected contract: after `Stop()`, any further `Stop()` should be a no‑op for the accumulated value unless `Start()` has been called again.

Additionally, after a `Stop()`, a subsequent `Start()` must reset the timing interval so that a later `Stop()` accumulates only the new elapsed time—not continue from the old start. Please update the timer to enforce these invariants.

v1/metrics: Fix restartable timer bug. (#7669)


We need a terse implementation request. Let's capture the observable behavior, the affected types (Set/Object iteration), the concurrency assumption, and the requirement that the sorting operation be safe for multiple concurrent readers without adding global locks or changing the lazy-sort behavior.

Here's a draft:

**Title:** Race condition on concurrent reads to Set/Object during iteration

**Description:**
When multiple goroutines call iteration methods (e.g., `Iter()`, `Foreach()`, `Slice()`, or `Sorted()`) on the same `Set` or `Object` instance built via the AST package, a data race occurs during the lazy key-sorting step. The symptom is that some goroutines observe out-of-order elements, miss elements, or the process crashes with a Go race-detector warning. The issue is reproducible by constructing a `Set`/`Object` (with no further mutations) and having several goroutines iterate over it simultaneously.

**Expected behavior:**
- The key slice must be sorted *at most once* between insert operations, safely, without races among concurrent readers.
- The single-writer, many-reader model must be preserved: concurrent `insert` calls are still unsupported, but concurrent readers alone must be safe.
- The fix must not eagerly sort on every insertion or iteration, to avoid performance regressions.

**Notes:**
- The existing `numInserts` counter in the set/object internals is a non-atomic int, which contributes to the race. The solution should replace that counting mechanism with a proper concurrency guard that allows multiple goroutines to attempt the sorting but ensures only one actually performs it (and that the result is visible to all).
- Both `set` and `object` types exhibit the same pattern and must be fixed in the same way.

That’s the request. It points to the right types and methods, describes the observable bug, and outlines the constraints without prescribing `sync.Once`. That should give the agent enough to navigate to `ast/term.go` and apply the fix.```
Concurrent calls to Set or Object iteration methods (Iter(), Foreach(), Slice(), Sorted()) on the same instance cause a data race in the lazy key-sorting step. The race can produce unsorted results, missing elements, or a crash when the Go race detector is enabled. The issue occurs even when no concurrent insertions happen—only multiple concurrent readers.

The underlying implementation uses a single-writer, many-reader model: insertions are not concurrent, but reads must be safe among multiple goroutines. The current lazy-sort guard (a non-atomic `numInserts` counter) is not safe for concurrent readers.

Fix the sorting step so that concurrent readers on a Set or Object trigger the sort at most once between insertions, without races, and without changing the lazy-sort performance characteristic (i.e., do not sort on every read or insertion). Apply the same fix to both `set` and `object` internals.
```

Fix multiple-reader race condition for Sets/Objects (#5016)


**Request: Type checker should infer union types for variables in heterogeneous collections**

When the OPA type checker processes rules or queries that iterate over a collection whose elements have differing types, the inferred type for a variable that unifies with those elements is currently incorrect. Instead of reflecting the full set of possible types the variable could take on (a union type), the checker retains only a single type, typically the type of the last element encountered. This causes the checker to miss potential type violations and to misreport the types of variables in later expressions.

**Desired behavior:**
- For any variable that is unified with elements of an array, values of an object, or members of a set where those elements/values can be of different types, the type checker should infer an `any<...>` type that is the union of all types the variable can take on.
- This unioning should happen even when the collection is built from a literal and iterated with a reference like `x[_]`.

**Acceptance criteria examples (observable outcomes):**

1. **Heterogeneous array:** Given `x = [[1], ["foo"]]; x[_] = [y]`, the inferred type for `y` must be `any<number, string>`, not just `number` or just `string`.
2. **Heterogeneous set:** Given `x = {[1], ["foo"]}; x[[y]]`, the inferred type for `y` must be `any<number, string>`.
3. **Heterogeneous object values:** Given `x = {"a": [1], "b": ["foo"]}; x[_] = [y]`, the inferred type for `y` must be `any<number, string>`.

These examples use heterogeneous collections of nested arrays, but the unioning should apply to any case where a variable receives values of incompatible types from different elements, keys, or values of a collection being traversed. The fix must ensure existing correct inference for homogeneous collections is not regressed.

Fix type inferencing to handle unioning properly


Update the copy propagation pass to suppress single-term expressions that consist of a variable already bound to a value. For example, after a rule like `input.x[i] = a; a.foo = 1`, the output currently includes a redundant `a` expression; these should be dropped.

Do not remove expressions that represent set iteration (e.g., `input.z[k]`) — those must remain because they have different evaluation semantics. Only kill when the expression is a bare variable term that is present in the propagation binding list (taking the union-find into account). The change should not affect any other expressions.

Update copy propagation to kill single term exprs


The wasm runtime’s `opa_malloc` implementation in `wasm/src/malloc.c` calls `__builtin_wasm_grow_memory` for every allocation, which is extremely expensive. This causes policy evaluation time to be dominated by `memory.grow`, making integration tests unacceptably slow.

Make the allocator track the heap and only call `memory.grow` when the heap pointer would exceed the current memory size. The new allocator must correctly handle multiple evaluations where memory pages have already been reserved but heap data from a previous evaluation may still be present. To prevent stale pointers, the `opa_object()` and `opa_set()` functions in `wasm/src/value.c` must set their `head` pointer to NULL after allocation so that reused memory does not contain garbage from a prior run.

wasm: Remove memory.grow calls on every malloc


Add `array.concat(arr1, arr2)` as a built-in function.
It must accept two arrays and return a new array containing all elements of the first array followed by all elements of the second, preserving order.
If either argument is not an array, the call should raise a runtime type error indicating which operand was invalid (e.g., “operand 1 must be array”).
Define the built-in in `ast/builtins.go` with a suitable type signature, implement it in the topdown evaluator, and register it so that it is available in Rego.
Follow the existing patterns for built-in functions and operand validation.