We currently lack a way to fund a PSBT by providing a raw list of inputs and outputs.
The `LndRpcClient` can already fund an existing PSBT via `fundPSBT(psbt, ...)`, but callers who want to specify individual `TransactionOutPoint`s and destination `BitcoinAddress`es have to manually construct a transaction and wrap it in a PSBT first.
This is cumbersome and misses the direct "raw funding" capability that LND’s `FundPsbtRequest` offers.

We need new overloads on `LndRpcClient.fundPSBT` that accept:
- `inputs: Vector[TransactionOutPoint]`
- `outputs: Map[BitcoinAddress, CurrencyUnit]`
- a fee rate in `SatoshisPerVirtualByte`
- an optional LND account string

Under the hood these should build a `TxTemplate` and use `FundPsbtRequest.Template.Raw`.

In addition:
- Make sure there is also an overload that takes a `PSBT` but adds the `account` parameter, so that all variants are consistent.
- Provide the implicit conversions between LND’s `OutPoint` and our `TransactionOutPoint` (both directions, plus the vector implicit mappings) so that building the raw template doesn’t require manual translation. These conversions belong in `LndUtils` alongside the existing output and channel-point helpers.

From a user perspective, after this change one should be able to call:

```scala
lnd.fundPSBT(inputs, outputs, feeRate, spendUnconfirmed = true)
// and with an explicit account:
lnd.fundPSBT(inputs, outputs, feeRate, "default", spendUnconfirmed = true)
```

The existing `fundPSBT(psbt, feeRate, spendUnconfirmed)` must continue to work; in particular funding a PSBT that has **zero inputs** (i.e., constructing a new transaction entirely from the wallet’s UTXOs) should succeed and return a fundable PSBT.

Add raw funding of psbt to LndRpc (#4235)


The `P2PClientActor` constructor and its companion factory methods (`P2PClient.props`, `P2PClient.apply`) accept five separate callback parameters (`onReconnect`, `onStop`, `onInitializationTimeout`, `onQueryTimeout`, `sendResponseTimeout`). This leads to repetitive, unwieldy parameter lists and complicates future additions or changes to these callbacks. The same callbacks are threaded through `PeerManager` → `PeerFinder` → `PeerData` → `P2PClient`, making many unrelated classes depend on the individual callback signatures.

Expected behavior: Group these callbacks into a single, reusable object (e.g., a case class or trait) that can be passed as one parameter. Provide a sensible default instance (all no-ops) for test and utility code. Update all call sites—including production wiring in `PeerManager`, `PeerFinder`, and `PeerData`, as well as test and testkit code—to use the new wrapper. The external behavior should remain unchanged; only the internal API should be consolidated.

Impact: Reduces boilerplate, makes the callback contract clearer, and makes it easier to add new callback hooks in the future without touching every intermediate class.

Create P2PClientCallbacks to encapsulate callbacks (#5084)


```
Fix: Lightning invoices with minimally-encoded feature bits fail to parse

Some valid BOLT 11 Lightning invoices contain a features field whose payload is encoded
with the minimal number of 5‑bit groups – meaning the total length of the feature bits is
not a multiple of 8.  Currently parsing these invoices fails because the internal
representation of the features tag assumes the payload is always a full byte vector and
requires a 5‑bit → 8‑bit → 5‑bit round‑trip, which is lossy for non‑byte‑aligned payloads.

Expected behavior:
- Parsing an invoice whose feature bits are minimally encoded must succeed (no parse
  failure / exception).
- The stored representation of the features tag must retain the original 5‑bit payload so
  that re‑encoding (e.g., for signature verification or reserialisation) produces the
  correctly‑truncated feature bits.
- Invoices that already have a full‑byte‑aligned features payload must continue to parse
  and round‑trip correctly.
- The public API for querying features as a byte vector (when needed) must still work,
  deriving the byte representation on demand from the preserved 5‑bit payload.

The fix should be local to the Lightning invoice parsing and tag handling code without
changing the invoice string format or the external API for constructing feature tags from
bytes where that is still valid.
```

Fix handling invoice with minimal encoding of feature bits (#5405)


Remove the redundant `txid` parameter from all `SpendingInfoDb` concrete case classes (`SegwitV0SpendingInfo`, `LegacySpendingInfo`, `NestedSegwitV0SpendingInfo`) and from `UTXORecord`. The `txid` is always equal to `outPoint.txIdBE` and already guarded by a `require` that fails on mismatch. Keep the `txid` method on `SpendingInfoDb` but derive it automatically from `outPoint.txIdBE` instead of requiring callers to pass it. Adapt every call site that constructs these types so it no longer passes a `txid` argument. Update the Slick table mapping in `SpendingInfoDAO` so the `txid` database column still gets written (using `outPoint.txIdBE`) and is ignored on read when constructing `UTXORecord`, matching the new case class shape. Remove the now-unnecessary `require` that enforced equality. This eliminates duplication, prevents accidental mismatches, and keeps the database schema unchanged.

Remove SpendingInfoDb.txid parameter (#4199)


```
Title: Block sync is very slow and crashes for large blocks due to per-tx script-pub-key lookups

Description:

When the wallet processes a new block, it inspects every transaction individually. For each transaction it queries the database to find which of its outputs match our wallet's script pub keys. This means hundreds or thousands of separate DB queries per block, which makes syncing extremely slow.

Additionally, when a block contains a very large number of distinct script pub keys (over 1000), the query that loads the known script pub keys fails because the generated `IN (...)` clause exceeds the database's limit (typically resulting in an SQL error or a crash). This happens in the script-pub-key lookup that is called during block processing.

Expected behavior:
- For a whole block, relevant script pub keys should be fetched once in advance (batching the query into manageable chunks if necessary to avoid hitting SQL limits).
- The pre-fetched set should then be reused for every transaction in the block, instead of making a separate query per transaction.
- Transactions that have zero outputs matching our wallet should be identified and skipped early, without attempting further (potentially expensive) processing.
- The same upfront-fetch optimization should also apply when processing a single transaction (e.g. via `processTransaction` or when handling our own outgoing transactions) to avoid redundant lookups.
- The DLC wallet's transaction processing (which extends the normal wallet processing) must also accept and use the precomputed relevant outputs so it benefits from the optimization.
- When calculating the incoming amount for a newly received transaction, only outputs that truly belong to addresses already tracked by our wallet should be counted (not simply any output whose script matches a known script pub key pattern). Currently the incoming amount can be inflated because it includes outputs that match the bare script but whose address is not in the address database.

Impact:
Massive reduction in block-processing time and elimination of crashes during sync for large blocks. The fix should be transparent to the rest of the wallet logic while keeping all existing behavior for transactions that actually contain relevant outputs.
```

2022 03 22 getrelevantoutputs upfront (#4219)


**Title:** gRPC server has no authentication; should support server password and require it on non-localhost binds

**Description:**

The bitcoin-s gRPC server currently accepts all requests without any authentication. This is a security problem, especially when the gRPC server is bound to a non-loopback interface (any host other than `localhost` or `127.0.0.1`). The existing HTTP API already uses the `bitcoin-s.server.password` configuration entry and requires authentication; the gRPC server should do the same.

**Expected behavior:**

1. The gRPC server must authenticate requests using the same server password as the HTTP API (the `bitcoin-s.server.password` configuration value).
   - The authentication mechanism should be HTTP Basic authentication over gRPC. The username should default to `"bitcoins"` (the client supplies the password; the username is fixed).
   - When authentication fails, the server must return a deterministic gRPC `UNAUTHENTICATED` status (not a generic HTTP error or a different gRPC status).

2. The gRPC CLI client must support a `--password` option so users can pass the password on the command line. When the password is provided, the client must attach the Basic authorization credentials to every gRPC call.

3. When the server password is empty (or not configured) but the gRPC server is bound to a non-loopback host (`rpchost` is not `localhost` or `127.0.0.1`), the server must refuse to start, failing with a clear error message. If the password is empty and the host is loopback, authentication is optional (no auth for backward compatibility).

4. The overall server lifecycle must remain correct: the gRPC server still needs to start, serve requests, and stop properly when the password is set. The existing gRPC service implementation (version, zipdatadir, etc.) must still work unchanged when authentication succeeds.

**Impact:** Without this, a gRPC server bound to a public or non-loopback interface exposes unrestricted wallet/node control. Adding password protection consistent with the existing HTTP RPC eliminates this gap.

Add password-based authentication to bitcoin-s gRPC server and CLI (#6334)


**Feature request: Robust handling of misbehaving peers during initial blockchain download**

**Problem statement**
When a peer repeatedly sends invalid block headers and exceeds the configured invalid‑message threshold (i.e., `PeerData.exceededMaxInvalidMessages` becomes true), the node currently disconnects that peer and starts a new sync *immediately*, while it is still processing the current message from the same peer. This creates a race condition that can leave the node in an inconsistent state, cause crashes, or make the syncing process hang.

We need the node to instead **decouple** the discovery of a misbehaving peer from the actual removal of that peer and the start of a new sync.

**Desired behaviour**

1. When a peer is determined to be misbehaving (invalid‑header limit exceeded), the sync state machine should transition to a dedicated state that *merely marks* the peer as misbehaving. No peer removal or re‑sync should be performed at this point.

2. After the state transition, the peer manager must notice the new state and:
   - Disconnect the misbehaving peer.
   - Start syncing from a different peer.

3. While the state machine is in the misbehaving‑peer state, any further data payload from **that specific peer** should be rejected (processing should fail fast).
   A payload from a *different* peer should reset the state to “done syncing” so that peer can be used for syncing.

4. Sync‑related operations (fetching compact filter headers, downloading blocks, etc.) must behave as if **no valid sync peer is available** while the state is misbehaving‑peer – i.e., they should either be skipped or signal an error, just as they do in the `DoneSyncing` state.

5. Timeout events that reference the misbehaving peer (header‑request timeout, filter‑header timeout) must trigger a sync from a new peer, the same way they would if the current sync peer had timed out.

6. When a peer stop event fires for a misbehaving peer, the node should cleanly remove the stopped peer without any error.

7. The existing sync progress through other peers must remain unaffected. The node must be able to complete a full sync even when one peer becomes misbehaving, as long as other peers are available.

**Non‑obvious edge cases / safety constraints**
- After the misbehaving‑peer state is set, the node must not attempt to request further headers, filter headers, or filters from that peer.
- If the misbehaving peer was the *current sync peer*, the peer manager must eventually (after disconnecting it) pick a new sync peer and resume sync. The transition should not leave the node stuck in “no sync peer” indefinitely unless no other peers exist.
- The state change must be atomic with respect to the event that triggered it – no other message from the same peer should be processed after the state is set.
- If `recoverInvalidHeader` (or equivalent logic) is called a second time on the same peer after it has already been marked misbehaving, the node must not crash or try to remove the peer again; it should detect the situation and raise an error or no‑op safely.

**Implementation hints (keep the change focused)**
- The change should be localised to the data‑message handler’s state machine and the peer manager’s reaction to state changes. Existing tests for syncing with uncached bitcoind should continue to pass and may need small adjustments to recognise the new state.
- Any helper method that existed solely to support the old immediate‑disconnect path (e.g., a method that wrapped `node.syncFromNewPeer()` on the peer manager) may become unnecessary and can be removed as part of the clean‑up.


**Summary**: The polling interval for `BitcoindRpcBackendUtil.startBitcoindBlockPolling` currently defaults to 10 seconds, which is suboptimal for regtest environments where blocks can be generated instantly.

**Observed behavior**:
- The method `startBitcoindBlockPolling` accepts an `interval: FiniteDuration` with a default of `10.seconds`.
- Callers who want faster polling on regtest must explicitly pass a shorter interval (e.g., `1.second`); otherwise they wait 10 seconds between block polls even on regtest.
- This makes regtest-dependent integration tests and local development unnecessarily slow unless every call site remembers to override the interval.

**Expected behavior**:
- When no explicit interval is provided, the polling logic should automatically choose a sane interval based on the bitcoind network:
  - For `MainNet`, `TestNet3`, and `SigNet`: keep `10.seconds`.
  - For `RegTest`: use `1.second`.
- If a caller supplies an explicit interval, that value takes precedence (backward compatibility).
- The network information is available from the `BitcoindRpcClient` configuration (e.g., `bitcoind.bitcoindRpcAppConfig.network`).

**Impact**:
- Simplifies all callers: the common case (auto-detect interval) requires no argument.
- Speeds up regtest-based tests and development without manual per-call interval tuning.
- Keeps the safe 10-second default for production networks where blocks arrive less frequently.

**Non-obvious constraints**:
- Must not change the public API in a breaking way; existing callers passing an explicit duration should continue to compile and behave identically.
- The interval selection should happen inside `startBitcoindBlockPolling` and not be pushed to every caller.

refactor: Change interval param of `BitcoindRpcBackendUtil.startBitcoindBlockPolling` (#5640)


Feature request: Add `onBlockProcessed` callback to `WalletCallbacks`

I want to be notified every time the wallet finishes processing a block, similar to the existing `onTransactionProcessed` callback.
Currently `WalletCallbacks` supports events for transaction processed, broadcast, reserved UTXOs, and new address generated – but nothing for blocks.

Please add an `onBlockProcessed` callback with these properties:

- The callback receives the processed `Block` object.
- The callback returns `Future[Unit]` (consistent with other wallet callbacks).
- Registration should follow the existing `WalletCallbacks` API, e.g., `WalletCallbacks.onBlockProcessed(myCallback)`.
- The callback must be composable: if multiple `onBlockProcessed` callbacks are registered (via `addCallbacks`), all of them should be invoked when a block is processed.
- The callback must fire **after** the wallet has successfully processed the block and updated its internal state. It should not fire if processing fails.
- Failure of a callback should not prevent execution of other callbacks nor prevent the wallet from reporting the processing as successful (same error-handling semantics as other `WalletCallbacks`).

This would allow external components (oracles, monitoring, UI refreshes) to react to fully confirmed block processing without polling.


**Request: Improve wallet rescan to avoid leaking addresses when rescanning an already-initialized wallet**

**Background**

A wallet's rescan operation (e.g., when using `rescanNeutrinoWallet`) is meant to re-scan the blockchain for transactions relevant to the wallet. Currently, each rescan generates a fresh batch of new receive and change addresses to scan. If a wallet already contains addresses from normal usage or a prior scan, this causes an unbounded growth in the number of addresses, potentially exceeding the wallet's gap-limit and interfering with later recovery operations. The rescan should be idempotent with respect to address discovery: it should scan transaction history for all addresses the wallet already knows about, without creating additional ones unless the wallet is completely empty.

**Observable requirements**

1. **Use existing addresses when available**: When a rescan is performed on a wallet that has at least one address (i.e., the wallet has been used or initialized before), the rescan must scan every existing address and every manually watched script public key, but it must **not** generate any new receive/change addresses in the process.

2. **Fallback for empty wallets**: If the wallet is entirely empty (no addresses, e.g. after restoring from a mnemonic seed for the first time), the rescan should still generate a configurable batch of new addresses, as it does today, to discover early transactions.

3. **No recursive address expansion on non-empty wallet**: The rescan logic that checks the address gap after scanning a batch and decides whether to recurse should treat a non-empty wallet as having already satisfied its address discovery requirement. After scanning a batch of existing addresses, the rescan should terminate without generating further addresses, regardless of the observed gap.

4. **Watched scripts are still included**: Regardless of whether the wallet is empty or populated, any script public keys that were added via `watchScriptPubKey` (or otherwise not tied to HD chains) must be included in the block matching.

**Non-obvious safety constraints**

- The change must not break the case where a wallet is partially populated but still needs additional scanning to find all used addresses within the gap limit. However, for simplicity and correctness, the desired behavior is that if the wallet contains addresses, no further generation occurs during that rescan. A future rescan triggered after some addresses are generated through normal usage will pick up those new addresses in its scan.
- The existing concurrency and startup safeguards (e.g., preventing overlapping rescans) must continue to work as before.
- The implementation must ensure that the address batch used for scanning is deterministic and based on persisted data, never leading to a state where some addresses are scanned but not stored.
- The change should not affect the behavior of `getNewAddress` or `getNewChangeAddress` outside the rescan path. The rescan should simply stop using those methods once a wallet has addresses.

**Out of scope**

- Changing how address gaps are calculated or how new addresses are generated for normal wallet operations.
- Modifying the wallet initialization or recovery routines except as needed to avoid address generation during re-scan.

Use the set of addresses in the wallet when rescanning rather than generating more (#4211)


```
FutureUtil.makeAsync currently does not handle exceptions thrown directly by the
supplied thunk. If the thunk throws, the exception can escape the executor's thread,
potentially killing it, and the returned Future never completes (the Promise is never
completed). This is inconsistent with the contract of an async computation where
synchronous exceptions in the body should be captured and turned into failed Futures.

Update makeAsync so that any exception thrown by the thunk is captured and results in
a failed Future with that exception, while the normal success path continues to work
correctly.
```

Have FutureUtil.makeAsync handle thrown exceptions (#4458)


**Title**: Validate `bitcoin-s.wallet.walletName` configuration value

---

The `bitcoin-s.wallet.walletName` config key is currently accepted without any sanity checks. A user can set it to any string, including values that contain whitespace or arbitrary special characters. Since the wallet name is used when constructing filesystem paths (and potentially in other naming-sensitive contexts), allowing such characters can cause hard-to-debug errors or unintended behavior.

**What needs to happen:**

- When the `bitcoin-s.wallet.walletName` configuration value is read, it should be validated.
- Only **alphanumeric characters, hyphens (`-`), and underscores (`_`)** are permitted in the name.
- **An empty string** should remain allowed (it indicates “no wallet name”).
- Values that consist entirely of whitespace, values that contain any other characters (e.g., `@`, `.`, spaces), and values with leading/trailing whitespace must be **rejected** with a clear error message.
- The validation should treat `None` (i.e., the key not being set) the same as an empty string — that is, it is acceptable.

**Acceptance criteria:**
- Setting `bitcoin-s.wallet.walletName` to something like `old-wallet` works.
- Setting it to `@@@`, ` old-wallet`, `old-wallet.`, or `       ` causes a startup failure with an informative message.
- Omitting the key entirely still works (no wallet name).

Validate bitcoin-s.wallet.walletName config option (#4336)


Add probing capabilities to `LndRpcClient`

As a developer using the bitcoin‑s LND RPC client (the `lnd-rpc` module), I need the ability to probe payment routes so I can verify connectivity and liquidity toward a node or for a specific invoice without performing a real payment.
Acceptance criteria

  1. **Probe by amount and node**
     `LndRpcClient` should expose a method with the signature
     `probe(amount: Satoshis, node: NodeId): Future[Vector[Route]]`
     that:
        • Queries routes for the given amount to the destination node.
        • For each returned route, sends a spoof HTLC using a **freshly generated, random payment hash** (so no actual invoice is matched).
        • Considers a route _viable_ only when the payment attempt fails with the LND failure code `INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS`.
        • Returns the list of viable `Route` objects.
        If no routes satisfy this condition, the returned vector is empty.

  2. **Probe by invoice**
     Provide an overload:
     `probe(invoice: LnInvoice): Future[Vector[Route]]`
     that extracts the amount (falling back to `0` satoshis if the invoice has no amount) and destination `NodeId` from the invoice, then behaves like the amount‑and‑node overload above.

  3. **Probe and pay**
     Add a method:
     `probeAndPay(invoice: LnInvoice): Future[Option[HTLCAttempt]]`
     that:
        • Probes the invoice to obtain viable routes.
        • Sorts the viable routes by `totalFeesMsat` ascending (cheapest first).
        • Attempts to pay the invoice using the routes **in order**.
        • If a payment attempt succeeds (status is `Succeeded`), returns `Some` with that payment attempt.
        • If no route was viable or all payment attempts fail, returns `None`.

  4. **Integration constraints**
        • All new methods must be directly callable on the existing `LndRpcClient` instance.
        • The implementation must respect the existing LND gRPC interaction patterns (i.e., using `lnrpc` and `routerrpc` services already available inside the client).
        • The existing public API of `LndRpcClient` must not be broken, and no unrelated behaviour should change.


```
Implement Map.prototype.getOrInsert and Map.prototype.getOrInsertComputed for the TC39 Upsert proposal (https://tc39.es/proposal-upsert/).

Add both methods to the Map built-in's prototype. The key behaviors:

- getOrInsert(key, value): If the map already contains the canonicalized key, return the existing value. Otherwise insert the (key, value) pair and return value.
- getOrInsertComputed(key, callback): First canonicalize the key. If the key exists, return the existing value without calling callback. If the key is absent, call callback(key) with this = undefined, then re-check whether the key now exists (the callback may have inserted it). If the key exists after the callback, replace its value with the callback result; otherwise insert the new (key, callback-result) pair. Throw TypeError if callback is not callable.
- Both methods must canonicalize -0⟶+0. The engine already does this for Set and in Map.set; extract the canonicalization helper (e.g., canonicalize_keyed_collection_value) to a shared location so it can be reused by Map, Set, and any other keyed-collection operations, and update the existing ad-hoc canonicalization in Map.set and Map.groupBy to use the shared helper.
- The methods must validate that `this` is a Map (TypeError otherwise).

Include tests that cover:
- Insert when key is missing, and return of the newly inserted value.
- Return of the existing value when the key is present (with no side-effect).
- Canonicalization: passing -0 must behave identically to +0.
- Handling of undefined as the value argument.
- TypeError when `this` is not a Map.
- TypeError for getOrInsertComputed when callback is not callable.
- getOrInsertComputed must not invoke the callback when the key already exists.
- Inside getOrInsertComputed, the callback's `this` must be undefined and the key argument must be the canonicalized value.
- Map mutation by the callback: if the callback inserts the same key, the final value must be the callback's return value, not a duplicate entry.
```

Implement upsert methods for Map (#4436)


We should make `RegExp` a first-class specialized object type, similar to how `ObjectData` already has variants for `Array`, `Map`, `String`, and `Boolean`. Currently, `RegExp` objects store their internal state through a combination of generic `InternalState` and internal slots, which is inconsistent with other built-in object specializations.

Acceptance criteria:
- `ObjectData` should gain a variant that holds the `RegExp` data directly, and `Object` should expose an accessor to check whether an object is a `RegExp` and to borrow a reference to its `RegExp` data (following the same convention used for `Map`, `String`, `Boolean`, etc.).
- The `RegExp` constructor (`make_regexp`) must store the newly created `RegExp` instance in the object’s `ObjectData` instead of setting internal slots and `InternalState`.
- All `RegExp` built-in methods (`test`, `exec`, `match`, `match_all`, `toString`) must retrieve the `RegExp` data using the new `ObjectData` accessors rather than `with_internal_state_ref` or internal-slot lookups.
- Any other built-in that inspects a value to decide if it is a `RegExp` (for example, the `String` prototype methods that handle a regexp argument) must use the new object-data check instead of probing internal slots such as `"RegExpMatcher"` or `"OriginalSource"`.
- The standalone `RegExp::create` function should be removed; initialization of the global `RegExp` constructor should happen entirely inside `RegExp::init`.
- The `RegExp.prototype` property getters for `dotAll`, `flags`, `global`, `ignoreCase`, `multiline`, `source`, `sticky`, and `unicode` should be temporarily removed. They depended on the old internal state access pattern and cannot be easily driven through the new `ObjectData` accessors until they are reimplemented as proper ECMAScript accessor properties (add a TODO to that effect).
- The `RegExp` struct must satisfy the `Trace` and `Finalize` traits required for GC integration.
- All existing `RegExp` behavior must continue working correctly.
- The `check_regexp_constructor_is_function` test that relied on `RegExp::create` should be removed, since that function no longer exists.


I need to resize `ArrayBuffer` objects from Rust code, but the engine’s public Rust API doesn’t expose the resize operation that is already available in JavaScript via `ArrayBuffer.prototype.resize`. The underlying `ArrayBuffer` data structure holds a `max_byte_len` to control whether the buffer is resizable, but there is no way to call resize on a raw `ArrayBuffer` or to create a resizable buffer through `JsArrayBuffer::from_byte_block` — the method doesn’t let you set a maximum byte length, so all buffers created that way are fixed-length.

I’d like:

1. A public `resize` method on `ArrayBuffer` that works directly from Rust, with the same semantics as the JavaScript spec:
   - If the buffer has no `max_byte_len` (i.e., it’s fixed-length), the call must fail with a `TypeError`.
   - If the buffer is detached, it must fail with a `TypeError`.
   - If `new_byte_length` exceeds the buffer’s maximum byte length, it must fail with a `RangeError`.
   - Otherwise, resize the underlying data (probably zero-filling for growth).

2. A way to construct a resizable `JsArrayBuffer` from a byte block. Today `from_byte_block` ignores maximum byte length; there should be some mechanism to set `max_byte_len` on the buffer so that it can later be resized via the new Rust API.

The JavaScript side’s `ArrayBuffer.prototype.resize` implementation should reuse this internal resize method rather than duplicating the validation logic.

Allow resizing of underlying ArrayBuffer from Rust (#4082)


**Title:** `new Response(body, { status: 205 })` should throw a `TypeError`, but it succeeds

**Description**
The [Fetch standard](https://fetch.spec.whatwg.org/#null-body-status) defines the *null body status* codes as `101`, `103`, `204`, `205`, and `304`. When constructing a `Response` with a body and any of these statuses, the constructor must throw a `TypeError`.

In the current implementation, `101`, `103`, `204`, and `304` correctly trigger a `TypeError`, but `205` (Reset Content) does not. Calling `new Response("body content", { status: 205 })` unexpectedly succeeds, creating a response that should be considered invalid per the spec.

**Steps to reproduce**
```js
// In a Boa runtime with the WebAPI fetch shim:
new Response("test", { status: 205 }) // should throw TypeError, but currently succeeds
```

**Expected behavior**
`new Response(anyBody, { status: 205 })` should throw a `TypeError`, consistent with the other null body statuses.

**Additional notes**
The error message shown for the other null body statuses lists the relevant status codes but does not include `205`. That message should be updated to mention `205` as well.

fix(fetch): add status 205 to null body status check in Response (#5208)


**Title:** Implement `Array.prototype.filter` following the existing callback-based array method pattern

**Description**

Add `Array.prototype.filter(callback [, thisArg])` to the Boa interpreter. This method should behave according to the ECMAScript specification: it creates a new array with all elements for which the provided callback function returns a truthy value.

Requirements:
- If no argument is passed (callback is missing), throw a `TypeError` with an appropriate message (similar to what `map`/`every` do).
- The callback signature is `callback(element, index, array)`.
- The optional `thisArg` is forwarded to the callback as its `this` value.
- Iteration proceeds from `0` to `length - 1`; for each index the element is retrieved and the callback is invoked via the interpreter’s call mechanism. If the callback result is truthy (use `is_true()`), the element is included in the result array; otherwise it is skipped.
- The original array must **not** be mutated.
- The result must be a proper Boa array (created with the standard array constructor helpers) whose elements are set in the correct order and whose `length` property reflects the filtered count.

The new built-in method should be exposed on the `Array` prototype alongside existing iterator methods (`map`, `every`, `forEach`, `find`, etc.) so that `[1,2,3].filter(v => v > 1)` works. Tests should verify:
- Filtering an empty array returns a new empty array.
- Filtering leaves the original array unchanged.
- Basic filtering with simple callbacks (including using the second `thisArg` parameter correctly).
- That calling `filter` with no arguments throws.

Please model the implementation after the already implemented `Array.prototype.map` and `Array.prototype.every` – following the same conventions for argument handling, callback invocation, array creation, and registration on the prototype.


Please implement the static method `Object.create(proto)` on the built‑in `Object` constructor. It should create a new ordinary object whose internal prototype is the provided argument.

Rules:
- If the argument is an object or `null`, return a new ordinary object with that value as its prototype.
- If the argument is anything else (e.g., `undefined`, a number, a string), throw a `TypeError` with a message indicating that the prototype must be an object or null.
- The function’s `length` property should be `2` (the spec accepts an optional second `properties` argument; we can leave that unimplemented for now).

[builtins - object] Object.create (#543)


**Request:** Fix `Context::run_jobs()` hang when a zero-delay `setInterval` is the only remaining work.

**Background**
On a Boa context built with default settings (i.e., using a real wall clock), the following pattern causes `run_jobs()` to spin forever at 100% CPU and never return:

```js
setInterval(() => {}, 0);
```

After `eval`’ing that script and calling `context.run_jobs()`, the call blocks indefinitely. The same scenario works correctly under a `FixedClock` (as used in existing interval tests) because the clock does not advance automatically, which masks the bug.

**Expected behaviour**
When the only remaining work in the job queue is a recurring timer (started by `setInterval`), the event loop should exit promptly. `run_jobs()` must not treat a past-due recurring timer as a reason to keep the loop alive. The recurring timer should still be dispatched and re-enqueued for the next interval, but its presence must not prevent termination when no non-recurring jobs or other real work remains.

**Acceptance criteria / invariants**

1. **Termination with real clock** – With a default `Context` (real clock), after scheduling `setInterval(fn, 0)`, `run_jobs()` must return after dispatching the callback at least once. It must not busy-loop.
2. **One-shot timers still block exit** – A past-due non-recurring timeout (created by `setTimeout`) must still cause the loop to continue running until that timeout has been serviced.
3. **No regression for fixed-clock tests** – All existing interval tests (which rely on `FixedClock`) must continue to pass, including those that verify interval cancellation, delay ordering, and timeout execution.
4. **Correct dispatch and re-enqueue** – Recurring timers are still executed and reinserted with their intended periodicity; only the loop-exit decision is affected.
5. **Clock advancement edge case** – When the real clock advances between iterations (even by nanoseconds), a zero-delay recurring timer that re-enqueues at `now + 0` must not be mistaken for unserviced work that prevents exit.

**Non-goals**
- Changing the public API of `Context`, `JobExecutor`, or any timer-related types.
- Altering the semantics of `setInterval` or `setTimeout` regarding when callbacks are fired.
- Modifying the way jobs are enqueued, split, or stored in the executor’s data structures beyond what is needed for the exit condition.

**Hints**
- The existing `TimeoutJob` struct already carries a flag to distinguish recurring from one-shot timers; that flag is not yet consulted during the event-loop termination check.
- The event loop’s “no more work” decision lives inside the job executor’s async run method; look for the point where it examines whether any timeout jobs are past-due and decide whether to break out of the loop.

fix(runtime): ignore recurring TimeoutJob in termination guard to prevent setInterval(0) infinite spin (#4749)


Make the `Logger` trait's methods accept a `&mut Context` and a read-only `ConsoleState` instead of `&Console`. Move the console's counters, timers, and group stack into a new `ConsoleState` struct with private fields and public accessor methods, so logger implementations cannot mutate the console state directly. Update `DefaultLogger`, the internal call sites in `Console`'s methods, and the test `RecordingLogger` to match the new signatures. Export `ConsoleState` from the crate. This is a breaking API change but should have minimal downstream impact.

Add context to the console Logger trait (#4005)


Add static methods `Object.getOwnPropertyNames` and `Object.getOwnPropertySymbols` to the Object builtin.

Both methods should accept a single argument, coerce it to an object (throwing a `TypeError` if the argument is `null` or `undefined`), and return an array of the object’s own property keys, filtered by key type.

- `Object.getOwnPropertyNames`: return all own property keys that are strings, including integer-index keys (e.g. `"0"`, `"1"`) as strings.
- `Object.getOwnPropertySymbols`: return all own property keys that are symbols.

Note the difference from the existing `Object.keys`, which returns only enumerable own string properties. These new methods return all own keys of the specified type, regardless of enumerability. The ordering must match `[[OwnPropertyKeys]]` (the engine’s own property key order).

Implement Object.getOwnPropertyNames and Object.getOwnPropertySymbols (#1606)


**Title:** Regex literals starting with `=` are misparsed as division‑assignment (`/=`)

**Symptom**
Regular expressions whose pattern begins with `=` are not recognized. For example, `var re = /=/;` fails to parse, and `class A extends /=/ {}` does not interpret `/=/` as a regex but instead treats it as the `AssignDiv` operator.

**Desired behavior**
In expression positions where a regular expression is expected, the lexer should treat `/=` as the start of a regex literal (pattern body beginning with `=`) rather than emitting an `AssignDiv` token. This should work in all places where a standalone `/` would start a regex, including primary expressions, `extends` clauses, arrow function bodies, and unary/binary expression operands.

**Constraints / edge cases**
- The existing behavior for real division‑assignment (e.g., `a /= 2`) must be preserved; the fix must only apply when the current grammar context expects a regex.
- Comments (`//`, `/* */`) that follow a `/` must still be recognized; the `/=` case should not interfere with comment handling.
- The regex body containing `=` must be lexed as a normal regex body (including any subsequent regex flags).
- No special‑case for just `=`; the approach should naturally extend to any pattern starting with `=` (e.g., `/==/`).

**References**
- [ECMAScript Lexical Grammar](https://tc39.es/ecma262/#sec-ecmascript-language-lexical-grammar) – regular expression literals are only recognized when the input element goal is `RegExp` or `HashbangOrRegExp`.

Bug fix: regex started with `/=` parsed as `AssignDiv` (#4048)


**Request**

Add field-level and container-level rename support to `TryFromJs` and `TryIntoJs` derive macros, and unify attribute naming across all macro frontends.

Currently these derives do not honour any rename attributes. In contrast, `boa_class` and `boa_module` allow per-item renaming via `#[boa(name = "...")]` and a container naming scheme via `#[boa(rename = "camelCase")]`. We want the same capability for the derives, but with clearer, consistent attribute names:

- Per-item/field override: **`#[boa(rename = "...")]`** (replaces the legacy `name` key)
- Container-level naming scheme: **`#[boa(rename_all = "camelCase")]`** for structs, and **`#[boa(rename_all_class = "...")]`** for module-level class renaming (replaces the legacy `rename`/`rename_class` keys)

Behaviour requirements:

1. For `TryFromJs` and `TryIntoJs` structs:
   - A field-level `#[boa(rename = "someName")]` selects the JavaScript property key for that field.
   - A struct-level `#[boa(rename_all = "camelCase")]` (or `"none"` / `"PascalCase"`) applies a naming scheme to all fields whose keys are not overridden.
   - The default when no scheme is given must be no renaming (`None`), unlike some existing macros that default to `camelCase` – keep the derive side conservative so existing usage is not broken.

2. Update `boa_class` and `boa_module` to use the new attribute names:
   - Each `#[boa(name = "...")]` becomes `#[boa(rename = "...")]`.
   - The container naming attribute `rename` → `rename_all`, and for modules the class naming attribute `rename_class` → `rename_all_class`.
   - All existing internal and external sites that use the old names must be updated, including tests and examples (the example binary `try_into_js_derive` should exercise the new feature).

3. Ensure that field-level `rename` takes precedence over the container scheme, and that any attribute parsing errors produce clear messages at the correct span.

No other derive macros need changes; this is specifically about `TryFromJs`/`TryIntoJs` and the existing class/module macros for consistency.

Add a #[boa(rename = ...)] attribute to TryFromJs and TryIntoJs derive macros (#4286)


**Bug Report: Missing `Map.prototype.keys()` and `Map.prototype.values()` methods**

### Description
The built-in `Map` object does not expose the standard `keys()` and `values()` prototype methods. Calling either method results in a `TypeError` because the methods are absent. This prevents using standard iteration protocols for retrieving only keys or only values from a Map.

### Steps to Reproduce
1. Create a `Map` with some entries.
2. Attempt to call `.keys()` or `.values()` on the Map instance.
   ```js
   const m = new Map([['a', 1], ['b', 2]]);
   m.keys();   // TypeError: m.keys is not a function
   m.values(); // TypeError: m.values is not a function
   ```

### Expected Behavior
- `Map.prototype.keys()` should return a new Iterator object that yields **only the keys** for each element, in insertion order.
- `Map.prototype.values()` should return a new Iterator object that yields **only the values** for each element, in insertion order.

Both methods should follow the same iterator protocol already implemented by `Map.prototype.entries()` and the existing Map iterator. They should not modify the Map instance and should handle empty Maps correctly (the iterator should immediately be exhausted, with `done: true`).

### Impact
Without these methods, code that relies on standard ES6 Map iteration for keys/values is broken. This makes the engine non-conformant with the ECMAScript specification for `Map.prototype.keys` and `Map.prototype.values`, and limits interoperability with scripts and libraries that expect these methods to exist.

### Notes
The existing `entries()` method and the underlying iterator infrastructure appear to already support different iteration kinds (key+value). Extending that to support key-only and value-only variants should be straightforward and consistent with the current implementation.

[exec Map] Map.prototype.values and Map.prototype.keys implementation (#874)


Title: Harden `JsNativeError::kind` field and provide a stable accessor

### Background
`JsNativeError` currently exposes its `kind` field as `pub`. This leaks internal representation and makes it difficult to evolve the error type without breaking external consumers that pattern‑match on the field directly.

### Desired change
Make the `kind` field private and add a public getter method so callers can still read the error kind without direct field access.

* Add a public method `kind(&self) -> &JsNativeErrorKind` on `JsNativeError`.
  – It should be `const` and `#[inline]` to preserve zero‑cost access, matching the style already used on other accessors in the error module.

### Internal migration
All places inside the repository that currently read the field directly must be updated to call the new method. This includes:

* The `boa_engine` crate itself:
  - The error module’s own doc examples (they serve as public documentation and must remain compile‑able and correct).
  - Test helpers in `lib.rs` (`run_test_actions_with` and similar) that currently match on or compare `native.kind`.
  - The URI built‑in tests (`core/engine/src/builtins/uri/mod.rs`) where `native.kind` is used in an assertion.
* The `boa_runtime` crate’s test helpers (`run_test_actions_with`) where the same comparison pattern is used.
* The `tests/tester` crate’s `is_error_type` function that inspects the error kind.

In every case the direct field access (`error.kind`, `native.kind`, `&error.kind`, etc.) should be replaced by a call to the `kind()` method.

### Verification
* Run `cargo test -p boa_engine`. All unit tests, including the URI tests and any doc‑tests in the error module, must pass.
* A wider full‑workspace run would be ideal to catch any other references, but at minimum the engine tests should be clean.
* No public API breakage: existing code that previously matched on `native.kind` should work unchanged after switching to `native.kind()` (the return type is identical).

feat(error): harden JsNativeError public API surface (#5012)


I need a `TypedJsFunction<A, R>` to be usable as a parameter in `boa_interop`-mapped Rust functions. Currently there is no `TryFromJs` implementation for `TypedJsFunction`, so when a JavaScript callback is passed, it cannot be converted to a typed wrapper.

Additionally, there is no `TryFromJs` for `()`, preventing use of unit return types for void callbacks.

Finally, it is common to pass a `TypedJsFunction` as an argument to another JS function; we need `From<TypedJsFunction<A,R>>` conversions into both `JsValue` and `JsFunction`.

Add TryFromJs for TypedJsFunction and more tests (#3981)


```
## Bug: Index out of bounds crash in class expressions with nested classes

When executing a class expression that defines a nested class inside the constructor or inside a static block,
the engine crashes with an index-out-of-bounds error from scope-index assignment.

### Steps to reproduce

Evaluate either of these expressions:

```js
new (class{constructor(){class D{}}})();
(class{static{class D{}}});
```

Both result in: `index out of bounds: the len is 0 but the index is 0`

### Expected behavior

No crash; the expressions should finish without error (the nested class `D` is just a harmless declaration).

### Root cause hint

The scope-index optimization pass does not always assign a function scope to class constructors and static blocks.
The bytecode compiler, however, treats those contexts as always having a function scope (they set `HAS_FUNCTION_SCOPE` unconditionally),
so the scope analyzer must match that expectation when assigning indices.
Currently the scope analyzer relies on `contains_direct_eval` to decide whether to force a function scope for
static blocks, and for class expression constructors it does not force a function scope at all.

### Required fix

Ensure that the scope-index layout treats **class expression constructors** and **class static blocks**
as unconditional function scopes, independent of direct eval. Other scope analysis (e.g., binding escape analysis)
should continue to use `contains_direct_eval` where appropriate and must not be altered.

### Additional affected patterns

Many variations involving a nested class inside a class expression constructor or static block trigger the bug, e.g.:

```js
new (class{ m(){} constructor(){class D{}} })()
new (class{ static m(){} constructor(){class D{}} })()
new (class{ get g(){return 1} constructor(){class D{}} })()
new (class{ #m(){} constructor(){class D{}} })()
new (class{ ['p']=1; constructor(){class D{}} })()
(class{ static{let v=1} static{class D{}} })
(class{static{let v=1;((v)=>{class D{}})(v)}})
```

All of them should be fixed by the same change.
```


I need a way to erase the concrete type of a garbage-collected pointer so that I can store heterogeneous `Gc<T>` values in the same collection (e.g. a recursive tree or linked list where nodes may hold different types). Currently `boa_gc` only exposes `Gc<T>`, which always carries the concrete type parameter; there is no public type-erased equivalent. The GC internals already use type erasure for tracing, so the underlying machinery can track the runtime type, but that information is not exposed to users.

Desired functionality:

1. **A type-erased GC pointer** – a new public type (`GcErased`) that can wrap any `Gc<T>` (where `T: Trace`). It must:
   - Be cloneable.
   - Participate in GC tracing (so any reachable object it points to is kept alive) and run finalization/drop correctly when the erasure pointer is no longer rooted.
   - Provide methods:
     - `ptr_eq` to compare identity with another `GcErased` (same allocation).
     - `type_id` to retrieve the `TypeId` of the inner concrete type.
     - `is::<T>()` to check if the erased pointer actually points to `T`.
     - `downcast::<T>()` that returns `Some(&Gc<T>)` if the type matches, or `None`.
     - an unsafe `downcast_unchecked` for cases where the caller has proven the type.

2. **Runtime type queries on `Gc<T>` itself** – for consistency, `Gc<T>` should also expose `type_id`, `is::<U>`, `downcast::<U>`, and an unsafe `cast_unchecked::<U>` that reinterprets a `Gc<T>` as a `Gc<U>` pointer (without checking). This is needed to safely convert between different `Gc` type parameters when the underlying allocation is known to be of a related type (e.g., using `#[repr(C)]` for C‑style inheritance between GC‑managed objects).

3. **Generalise `Gc::ptr_eq`** – currently `Gc::ptr_eq` requires both sides to have the same generic parameter. It should be possible to compare `Gc<T>` and `Gc<U>` for pointer identity, since two differently‑typed `Gc` pointers can refer to the same allocation.

4. **Clean up the helper `addr_eq`** – the crate’s private `addr_eq` function was a workaround for an older Rust that lacked `std::ptr::addr_eq`. Now that `std::ptr::addr_eq` is available, the custom helper should be removed and existing call sites replaced with the standard library version, purely to reduce unnecessary code.

All of this should behave correctly under garbage collection: type‑erased pointers must be properly traced, finalized, and dropped. The implementation must ensure that constructing a `GcErased` from a `Gc<T>` does not double‑free or leak (the original `Gc<T>` should be consumed or forgotten appropriately).


**Title:** Replace internal usage of deprecated `I18n::translator()` with `getTranslator()` / `setTranslator()`

**Background**
The `I18n::translator()` method historically served as both a getter and a setter, depending on whether a `$loader` argument was provided. This dual-purpose interface is confusing and error-prone. A separate getter (`getTranslator()`) and setter (`setTranslator()`) have been introduced to clarify the API. The old method remains available but is now deprecated and should no longer be used internally.

**Acceptance Criteria**
1. All core and plugin translation helper functions (`__()`, `__n()`, `__d()`, `__dn()`, `__x()`, `__xn()`, `__dx()`, `__dxn()`) use `I18n::getTranslator()` instead of `I18n::translator()`.
2. Internal code in `I18n::setTranslator()` and `I18n::getTranslator()` does not call the deprecated `translator()` method; instead it interacts with the translator collection directly via `static::translators()`.
3. All test cases that previously registered translators via the old signature (`I18n::translator('name', 'locale', $loader)`) are updated to `I18n::setTranslator('name', $loader, 'locale')` to reflect the new argument order (`$loader` before `$locale`).
4. Tests that retrieve a translator are updated to `I18n::getTranslator(...)`.
5. The deprecated `translator()` method continues to function (backward compatibility), and at least one explicit test verifies that it returns the same instance as `getTranslator()` for the default case.

**Notes / Constraints**
- The new `setTranslator()` signature is:
  `public static function setTranslator(string $name, callable $loader, ?string $locale = null): void`
  Be mindful of the argument order when replacing old `translator()` calls that registered loaders.
- The `getTranslator()` signature is:
  `public static function getTranslator(string $name = 'default', ?string $locale = null): TranslatorInterface`
  No argument order change here.
- Do not modify the public API of `translator()` itself; it should remain as a deprecated passthrough.

**Affected files (non-exhaustive)**
- `src/I18n/I18n.php` – update `setTranslator()` and `getTranslator()` internals.
- `src/I18n/functions.php` – all global translation functions.
- `tests/TestCase/I18n/I18nTest.php` – replace old calls with the new getter/setter.

Update uses of translator() to use non-deprecated method.


We need a safe way to gradually adopt the upcoming `has()` behavior change planned for CakePHP 5, where explicitly setting a field to `null` makes `has()` return `true`. Today, `EntityTrait::has()` uses a `get($prop) === null` check, so `null` fields and truly missing fields are indistinguishable. For large applications, this change represents a dangerous cross-cutting concern that is hard to migrate all at once.

Please provide an opt‑in mechanism on the entity – for example, a protected `$_hasAllowsNull` boolean flag (default `false`). When enabled, `has()` should determine existence by checking whether the property key is present in `_fields` or whether a virtual getter method exists (`array_key_exists` logic), rather than evaluating the property value. This allows us to enable the 5.x behavior per entity class or dynamically, so we can audit and adapt our code incrementally, instead of waiting for a single, high‑risk version upgrade.

The new path must **only** affect `has()`; methods like `isEmpty()` and `hasValue()` must remain unchanged to preserve their current semantics. Virtual fields with accessor methods must still be recognized as “existent” under the new flag.

4.5 - Add opt-in path for EntityTrait::has()


### Update compatibility with Chronos 2.next

**Background**
Chronos 2.next deprecates the `timezone()` method in favor of `setTimezone()`. CakePHP currently uses `timezone()` in several places, which will cause deprecation notices or breakage when the new Chronos is released.
Additionally, `DateType::marshal()` delegates to `DateTimeType::marshal()`, which can return a `DateTimeImmutable` containing a time component – violating the date-only contract of the `Date` type. We need a marshal implementation that guarantees only date instances (e.g. `FrozenDate`) are produced, with no time portion.

**Required changes**

1. **Replace deprecated `timezone()` calls with `setTimezone()`**
   Locate every usage of `->timezone(...)` on Chronos/I18n date/time objects and update it to `->setTimezone(...)`.
   Affected areas:
   - `DateFormatTrait::i18nFormat()`
   - `RelativeTimeFormatter::timeAgoInWords()` and `dateAgoInWords()`
   - `TimeHelper::fromString()`, `TimeHelper::toAtom()`, `TimeHelper::toRss()`, and `TimeHelper::timeAgoInWords()`

2. **Provide a dedicated `marshal()` in `DateType`**
   The current implementation inherits `DateTimeType::marshal()`, which falls back to `DateTimeImmutable`. Override `marshal()` so that:
   - Input of type `DateTimeInterface` is converted to a `FrozenDate` (or the configured date class).
   - String, integer, and array inputs are parsed directly into the date class without ever creating a DateTime object.
   - Invalid array formats (non-numeric year/month/day, missing ‘day’ when required) must return `null` instead of silently producing a date from today’s date or an invalid object.
   - The method must still honor locale marshalling when `_useLocaleMarshal` is enabled.

3. **Adjust `TimeHelper::timeAgoInWords()` call-site for generic DateTimeInterface objects**
   The method currently checks `$dateTime instanceof DateTimeInterface` before calling `setTimezone`. Since Chronos 2.next introduces date-only objects that may not have a timezone method, change the check to `method_exists($dateTime, 'setTimezone')` to avoid errors when a plain `DateTimeInterface` without a timezone is passed.

4. **Update Chronos dependency**
   Change the composer requirement from `"cakephp/chronos": "^2.2"` to `"cakephp/chronos": "dev-2.next as 2.5.0"` until a stable tag is available for the new major version.

**Acceptance criteria**
- No deprecation warnings related to `timezone()` when running with Chronos 2.next.
- `DateType::marshal()` never returns an object with a time component; all returned dates represent midnight or a date-only value.
- Invalid array inputs (e.g. `['year' => 'farts', 'month' => 'derp']`) marshal to `null`.
- `TimeHelper` gracefully handles date/time objects that lack `setTimezone`.

Fix compatibility issues with Chronos 2.next


Make `Controller::paginate()` use the `Cake\Datasource\Paginator` class directly, bypassing the deprecated `PaginatorComponent`.

Requirements:
- The object to paginate can be a `Table` instance, a `Query`, a string table name, or `null` (use the default table). When given a string or `null`, resolve the table via `fetchTable()`.
- Merge the `$settings` argument with the controller’s `$paginate` property. That property should be `protected` and may include keys such as `limit`, `maxLimit`, `page`, `allowedParameters`, and a `paginator` class name to use.
- Instantiate the paginator (default to `Paginator::class` if no custom class is given) and invoke its `paginate()` method with the request’s query parameters and the merged settings.
- If the paginator throws a `PageOutOfBoundsException`, catch it and throw an `Http\Exception\NotFoundException` to produce a 404 response.
- Store the resulting paging parameters in the request attribute `paging`, merging with any pre-existing data.
- Remove all involvement of `PaginatorComponent` from this method.

Update Controller to directly use the Paginator class.


**Title:** FormProtectionComponent does not consider unlockedFields configuration during token generation

**Description:**

When the `FormProtectionComponent` is configured with `unlockedFields`, tokens generated by the component (e.g., via `_Token` fields in forms) do not reflect the unlocked fields configuration. As a result, a token generated in a context where no fields are unlocked will pass validation on a controller action that actually has one or more unlocked fields. This undermines the security guarantee because tampered or missing unlocked-field data is not detected, and an attacker could reuse a token from a page without unlocked fields against an endpoint that expects unlocked fields.

**Steps to reproduce:**

1. Configure `FormProtectionComponent` on a controller, with `unlockedFields` set to `['some_field']`.
2. Render a form on a page that does not include the unlocked field(s) (e.g., the token is generated without any unlocked fields). The resulting form token effectively encodes an empty unlocked-fields list.
3. Submit that form to an action that does have unlocked fields configured (the same `FormProtectionComponent` instance).
4. Observe that the request is accepted (200) instead of being rejected with a `BadRequestException` about a mismatch in unlocked fields.

**Expected behavior:**

A token generated with an empty unlocked-fields list should **not** be accepted by a controller action that has unlocked fields configured. The component should include the current `unlockedFields` configuration when constructing the token (via `FormProtector`) so that any mismatch between the token's unlocked fields and the action's unlocked fields causes a validation failure (e.g., “Missing unlocked field” or equivalent).

**Actual behavior:**

The token is accepted, and the request is processed normally. No exception is raised, allowing potentially tampered or replayed form data to bypass the protection.

**Additional context:**

- The issue is present in `FormProtectionComponent::startup()`, where the `FormProtector` instance is created without passing the component’s configuration (including `unlockedFields`).
- The related `FormProtector` class supports receiving `unlockedFields` in its constructor, but the component does not utilize it.
- This affects all actions protected by `FormProtectionComponent` that rely on unlocked fields. The fix should ensure that form tokens are always generated with the unlocked fields active at the time of token creation, and validation compares the token’s unlocked fields against the action’s current unlocked fields.

**Impact:**

Security-critical; may allow form tampering or cross-request token reuse when unlocked fields are used.

Fix FormProtection not comparing unlocked fields properly


Add an `enableOrmDelete` config option to `TreeBehavior`. When `true`, deleting a node (and its subtree) must remove child records using the Table’s `delete()` method instead of a raw SQL `DELETE`. This ensures that application-level callbacks (`beforeDelete`/`afterDelete`) and dependent association cascades are invoked for each removed child. The option should default to `false` to preserve the existing direct‑SQL behavior.


We need to safeguard the ORM’s table locator against infinite recursion that can occur when a Table’s `initialize()` method tries to fetch the same table class again. Currently, this scenario results in a fatal error or memory exhaustion with no clear indication of the problem.

## Expected Behavior

- **Recursive fetch blocked with a clear exception**
  When `TableLocator::get()` is called for a table class that is already in the middle of its own initialization (i.e., its `initialize()` is executing on the call stack), the locator must immediately throw a `RuntimeException` with a descriptive message, instead of proceeding.

- **Recursion is detected by resolved class, not just alias**
  The check must be based on the concrete Table class name. If a different alias resolves to the same class that is currently being initialized, the recursive call is still detected and blocked.

- **Normal usage unaffected**
  After a table is fully constructed, subsequent calls to `get()` for the same alias (or class) must return the cached instance without error, as they do today.

- **Unrelated table fetches during initialization are allowed**
  A table’s `initialize()` may still fetch other, unrelated table classes normally. Only recursive fetches that target the same concrete class must be blocked.

- **Tracking state is reset on clear**
  When the locator is cleared (e.g., via `clear()`), any internal recursion tracking must be reset so that the same table class can be constructed from scratch in a fresh context without false positives.

 ## Affected Component

 `Cake\ORM\Locator\TableLocator` – particularly the `get()` and `clear()` methods, and the path through `createInstance()`.

 ## Acceptance Criteria

 1. If a table’s `initialize()` calls the locator’s `get()` for a table name that resolves to the exact same concrete class, a `RuntimeException` is thrown.
 2. The same exception is thrown if the re-entrant call uses a different alias or casing that resolves to the same class.
 3. Calling `get()` repeatedly after initial construction works as before (no exception).
 4. `clear()` removes all tracked in-progress initializations so that the guard does not persist across clearing boundaries.
 5. Fetching a different table class during `initialize()` is not affected.

Detect infinite recursion when initializing Table


**Title:** Add a JsonFormatter for structured log output

**Context:**
Log engines (`BaseLog`, `ConsoleLog`, etc.) already support pluggable formatters via the `formatter` config option. Currently only a line-based `DefaultFormatter` ships, which is not optimal for machine parsing or log aggregation pipelines.

**Request:**
Implement a `JsonFormatter` that extends `AbstractFormatter` and lives in `Cake\Log\Formatter`. It should emit a JSON object per log entry containing:

- `date`: rendered using a configurable date format string (default: `DATE_ATOM`)
- `level`: the string representation of the log level as received
- `message`: the fully-interpolated message string (context interpolation is handled by the engine; the formatter receives the final message)

The JSON encoding flags should be configurable via constructor options (default: `JSON_UNESCAPED_UNICODE`).

**Acceptance criteria:**
- The formatter respects the `format($level, string $message, array $context = []): string` signature required by `AbstractFormatter`.
- It is usable with any log engine that accepts a `formatter` configuration array (e.g., `'className' => JsonFormatter::class, 'flags' => …`).
- Defaults produce a JSON object with `date`, `level`, and `message` keys, where the date is in `DATE_ATOM` format.
- The `context` parameter is acknowledged but does not need to appear in the output (the message already contains the relevant substitution).


**Title:** Hardening `RequestHandlerComponent::convertXml` against XML Entity Expansion (Billion Laughs)

**Background**
The `RequestHandlerComponent::convertXml` method parses XML request bodies using the `Xml` utility. Currently it uses `SimpleXMLElement` under the hood (the default `return` mode). `SimpleXMLElement` does not fully prevent nested internal entity expansions, allowing a “Billion Laughs” style attack that can exhaust memory and CPU.

Additionally, the `Xml::_loadXml` helper ignores any `LIBXML_*` flags (e.g., `LIBXML_NOENT`, `LIBXML_NONET`) when building a `DOMDocument`, meaning entity substitution cannot be reliably disabled for DOM parsing paths either.

**Desired Behavior / Acceptance Criteria**
1. **Safe entity expansion in request XML**: When an attacker supplies XML containing deeply nested entity definitions, `convertXml` must return an empty array `[]` rather than attempting to expand the entities. This prevents denial-of-service via memory/CPU exhaustion.
2. **Keep existing XML-to-array conversion working**: Legitimate XML input (with no entity declarations or only safe ones) must continue to be converted to arrays exactly as before, including the existing data-wrapping behavior for `<data>` root elements.
3. **Pass through security flags in DOM loading**: When `Xml::build` is called with options that include `LIBXML_*` flags, `Xml::_loadXml` must forward those flags to `DOMDocument::loadXML`. Currently they are silently dropped for DOMDocument, making it impossible to disable entity loading even when desired.
4. **No regression for `SimpleXMLElement` paths unless they are intentionally avoided**: The default `return` mode can remain as is, but the `RequestHandlerComponent` must explicitly use the DOMDocument path to benefit from its safer entity handling.

**Implementation Hints**
- In `RequestHandlerComponent::convertXml`, switch to calling `Xml::build($xml, ['return' => 'domdocument', 'readFile' => false])` and then inspect `$xml->childNodes->length`. If it is 0 (which happens when `loadXML` bails out due to entity expansion protections), return `[]`.
- In `Xml::_loadXml`, pass the `$flags` variable to `$xml->loadXML($input, $flags)` when creating a `DOMDocument`. This ensures flags like `LIBXML_NOENT` and `LIBXML_NONET` can be honoured.
- No changes to the public API signatures are required.


As a developer integrating with an OAuth 1.0-protected API via CakePHP’s HTTP client, I need to POST request bodies that include nested associative arrays without the OAuth signature calculation throwing errors or corrupting the signature.

### Current behavior
When a `Cake\Http\Client\Request` is sent with a body containing a multi-level associative array (for example, `[ 'search' => [ 'filters' => [ 'field' => 'date', 'value' => 'one two' ] ] ]`), the `Cake\Http\Client\Auth\Oauth` class fails to compute the signature base string. An “Array to string conversion” error is raised because the normalization logic cannot handle nested arrays.

### Expected behavior / acceptance criteria

1. **No errors on nested arrays**
   The OAuth class must **not** emit any error or exception when the request body contains arbitrarily nested associative arrays.

2. **Square-bracket notation for nested associative keys**
   Nested arrays with non‑integer (associative) keys are to be flattened into parameter pairs using PHP’s common square‑bracket notation.
   Example:
   `search[filters][field]=date`
   `search[filters][value]=one two`

3. **Backward compatibility for list (numeric) arrays**
   Arrays with numeric keys (plain lists) must still produce repeated parameters without brackets, exactly as they do today.
   Example: `tags=oauth&tags=cake`
   This ensures existing integrations are not broken.

4. **OAuth‑compliant parameter sorting**
   The parameter pairs must be sorted following RFC 5849 §3.4.1.3.2:
   - Within each nested array, keys are sorted lexicographically (case‑sensitive, using `uksort`).
   - After flattening, all parameter pairs are sorted by the entire “key=value” string (key first, then value for identical keys).

5. **Correct integration with other parameters**
   All parameter sources—query string parameters, OAuth parameters (from the credentials), and POST body parameters—must be merged and normalized together. The resulting normalized parameter string must be identical to what a standard OAuth 1.0 client would generate when receiving the same nested body data.

6. **No regression for flat bodies**
   Requests with a flat (non‑nested) body or with only list‑style arrays must continue to produce the same signature base strings as before.

Fix nested parameter encoding in Oauth1 client.


```
Add support for non-interactive I/O when `--quiet` is used.

When a command is invoked with `--quiet`, interactive prompts (`ask`, `askChoice`) should not wait for user input. Instead, they should return the provided default value immediately (or an empty string if no default). This prevents commands from hanging in non-interactive environments (e.g., CI, cron).

- `ConsoleIo` needs an internal interactive flag (default `true`) and a public setter.
- `BaseCommand::run` should set the flag to `false` when the `quiet` option is present.
- When the interactive flag is `false`, `_getInput` must skip the actual input read and return the (string-cast) default value.
```

Add SHells interactive false back for Commands


The `save`, `update`, and `delete` operations for tables with non-string primary key columns (e.g., `date`, `json`) emit a PHP warning:

```
strlen() expects parameter 1 to be string, object given
```

This occurs because the internal primary-key filtering applied during insert/update uses `strlen` as a callback for `array_filter`, which cannot safely handle non-string scalars or objects such as `DateTime` instances.

### Acceptance criteria

1. **No warnings on save/update/delete with complex keys**
   When a Table has a primary key column typed as `date` (or any non-string type that results in an object-like value), calling `Table::save()` with a new entity, updating an existing entity, or deleting an entity must complete without emitting any PHP warnings, notices, or errors related to `strlen` or string conversion of the primary key value.

2. **Correct persistence behavior**
   Saving a new entity must correctly insert the provided primary key value into the database. Updating an existing entity must locate and modify the correct row without data corruption. Deletion must remove the intended row. No regressions in standard cases (string and integer primary keys) are acceptable.

3. **Null primary key values are still excluded**
   When building the data array for an INSERT or UPDATE, any primary key fields whose value is `null` must be omitted from the data sent to the database. Other false-like values that are valid column values (e.g., empty string `''`, integer `0`) must be retained. This behavior must be independent of the primary key’s column type.

### Constraints

- The fix must not break existing behavior for tables with conventional primary key types (integer, string, uuid as string).
- No special configuration or schema changes should be required; the ORM should handle any column type for which a database type class returns an object from `Type::build()` without manual intervention.
- The filtering logic must not rely on casting primary key values to string.


**Title:** Add `getConfigOrFail()` to `InstanceConfigTrait` and `StaticConfigTrait`

**Description:**
When using `InstanceConfigTrait` or `StaticConfigTrait`, retrieving a required configuration key via `getConfig()` silently returns `null` if the key is missing or if its value is explicitly `null`. This can lead to hard-to-diagnose failures later. A dedicated method that throws an exception when the configuration is not present or is `null` would make missing configuration fail fast and improve code safety, similar to the existing `Configure::readOrFail()`.

**Expected Behavior:**
1. Add an instance method `getConfigOrFail(string $key)` to `InstanceConfigTrait` that:
   - Returns the configuration value if it exists and is **not** `null`.
   - Throws an `InvalidArgumentException` if `$key` is `null`.
   - Throws an `InvalidArgumentException` with a message like `"Expected configuration 'someKey' not found."` when the key is absent or its resolved value is `null`.

2. Add a static method `getConfigOrFail(string $key)` to `StaticConfigTrait` that:
   - Returns the entry from the static config array if the key is set and the value is not `null` (i.e., `isset` returns `true`).
   - Throws an `InvalidArgumentException` if `$key` is `null`.
   - Throws an `InvalidArgumentException` when the key is missing or its value is `null`.

**Why this is needed:**
- Prevents accidental use of incomplete configuration.
- Allows libraries that rely on these traits to enforce mandatory settings at initialization time rather than producing cryptic errors downstream.
- Consistent with the “fail early” pattern already established by `Configure::readOrFail()`.


**Title:** Route Middleware Grouping

**Context:**
`RouteCollection` and `RouteBuilder` already support registering individual middleware callables and applying them to paths via `applyMiddleware()`. Repeating the same list of middleware names across multiple paths or scopes is cumbersome and error-prone.

**Request:**
Add support for **middleware groups** – named sets of pre-registered middleware that can be applied as a single unit.

**Behavioral Changes:**

1. **Define a middleware group**
   - Introduce a method `middlewareGroup(string $name, array $middlewareNames)` on both `RouteCollection` and `RouteBuilder`.
   - Each middleware name in the list **must** already be registered as an individual middleware (via `registerMiddleware`). If any name is not registered, a `RuntimeException` is thrown.
   - The group name **must not** clash with an already registered middleware name, and vice versa. Attempting to create a group with a name that already exists as middleware (or reusing an existing group name) throws a `RuntimeException`.

2. **Check group existence**
   - Provide `hasMiddlewareGroup(string $name): bool` on `RouteCollection` (and RouteBuilder should expose it) so developers can introspect groups.

3. **Apply a middleware group to a path**
   - Extend `applyMiddleware(string $path, array $middleware)` so that each element can now be either an **individual middleware name** or a **group name**.
   - When resolving the array, if a name is not a registered individual middleware, the collection must check whether it is a registered group; if neither exists, throw a `RuntimeException` with an appropriate message.
   - The order of middleware when a group is applied should be the order of the names within the group definition, mixed in with any individual names from the same call.

**Safety / Edge Cases:**
- Preventing overlap between middleware names and group names is critical to avoid ambiguous resolution.
- All middleware in a group must be registered at the time the group is created (no lazy resolution).
- The existing behavior of `applyMiddleware` for individual middleware names remains unchanged.

**Impact:**
Reduces duplication when configuring middleware for routes, especially in plugins or large applications. No breaking changes to the existing middleware API.


Debugger’s stacktrace output does not match how PHP itself renders traces:

- The method‑call operator is always `::`, even for instance methods. PHP uses `->` for instance calls and `::` for static calls.
- The top‑most frame (the actual call site) is offset by one, because the formatting logic looks ahead to the next frame instead of rendering the current frame.
- When displaying a chain of exceptions, the trace of a previous exception is repeated in the child, making the output noisy.

Requirements:

1. `Debugger::formatTrace()` and any callers that produce string stacktraces must use `->` or `::` as appropriate, based on the `type` key in each frame.
2. The first line of a trace should correspond to the frame that directly represents where the error or exception occurred, not the next frame.
3. In both the HTML dev‑error template (`dev_error_stacktrace.php`) and the console exception renderer (`ConsoleExceptionRenderer`), chained exception traces must eliminate frames that already appear at the end of the parent’s trace. Two frames are equal when their `file` and `line` match.
4. The plain‑text error rendering logic (`PhpError::getTraceAsString()`) should handle frames that lack a line/file gracefully (e.g. the final `[main]` frame), avoiding the placeholder `??` / `[internal]` when the line is empty.

All existing stacktrace output formats (`txt`, `array`, `points`, and the `[main]` line) must be updated. The trace exclusion feature (`exclude` option) must continue to work with the corrected operator style.


## Feature Request: Convert `Cake\Utility\Filter` to namespaced functions with global aliases

**Context**
`Cake\Utility\Filter` was introduced in 5.1 as a static utility for safely converting mixed values to `string`, `int`, or `bool`. The project already has a convention for plain global helpers (e.g. `h()`, `env()`, `pr()`) and their namespaced counterparts in `Cake\Core\functions.php` and `Cake\Core\functions_global.php`.

**Desired behavior**
Move the conversion logic out of the `Filter` class and into three new functions:

- `Cake\Core\toString(mixed $value): ?string`
- `Cake\Core\toInt(mixed $value): ?int`
- `Cake\Core\toBool(mixed $value): ?bool`

Each function must preserve the exact same conversion rules and edge cases that the current `Filter` static methods implement, including:

- **`toString`**
  - Strings return as-is.
  - Integers and booleans convert to string equivalents (`true` → `'1'`, `false` → `'0'`).
  - Floats: `NaN`, `+INF`, `-INF` must return `null`. Other floats must be rendered without loss of precision, avoiding scientific notation when possible.
  - `Stringable` objects are cast to string; other non-convertible types return `null`.

- **`toInt`**
  - Integer input returns as-is.
  - Strings are validated via `FILTER_VALIDATE_INT` with `FILTER_NULL_ON_FAILURE`; leading/trailing whitespace is trimmed by the filter; `PHP_INT_MIN` as a return value signals failure and must yield `null` (because `filter_var` can return `PHP_INT_MIN` on overflow).
  - Floats must only convert if they fall within the safe integer range `[-2^53+1, 2^53-1]` (i.e. `±9007199254740991`); otherwise `null`.
  - Booleans convert to `1`/`0`; all other types return `null`.

- **`toBool`**
  - Only exact values `1`, `'1'`, `1.0`, `true` are `true`; only `0`, `'0'`, `0.0`, `false` are `false`.
  - All other inputs (including `"true"`, `"false"`, non-zero integers, non-zero floats, arrays, objects) must return `null`.

**Global aliases**
Following the existing project pattern, add `toString()`, `toInt()`, `toBool()` as global function aliases in `Cake\Core\functions_global.php`. These should delegate to the namespaced functions (using `use function Cake\Core\toString as cakeToString`, etc.) and be guarded with `function_exists` checks. Update the root `src/functions.php` if needed to ensure these global aliases are loaded automatically.

**Cleanup**
Remove the `Cake\Utility\Filter` class entirely. Update documentation and any references to point to the new functions.

**Acceptance criteria**
- Calling `Cake\Core\toString($value)` behaves identically to the old `Filter::toString($value)`, and the same for `toInt` and `toBool`.
- Global `toString()`, `toInt()`, `toBool()` are available and behave identically.
- No `Filter` class remains in the codebase.


**Title:** Tuple comparison transformation must reject unsupported operators (`NOT IN`, etc.) instead of silently producing incorrect SQL

### Context
The CakePHP database layer automatically transforms composite-key tuple comparisons for SQLite and SQL Server drivers. Currently, the transformation silently proceeds for any operator, but the generated SQL is only correct for `IN` and `=`. When a user passes `NOT IN` (or any other operator) in a tuple comparison with multiple fields, the query executes without error but yields wrong results or invalid SQL on these platforms.

### Desired Behavior
When a `TupleComparison` with a composite field list is being transformed (in `TupleComparisonTranslatorTrait::_transformTupleComparison()`), only the `IN` and `=` operators should be accepted. Any other operator must cause an immediate `RuntimeException` with a clear message indicating the operator is not supported, before the query is executed. This prevents silent data corruption or confusing database errors.

### Acceptance Criteria
1. Using `IN` or `=` in a composite-field tuple comparison on SQLite or SQL Server continues to work as before, no change.
2. Using any other operator (e.g., `NOT IN`, `<>`, `>`, etc.) triggers a `RuntimeException` with a message matching:
   `"Tuple comparison transform only supports the 'IN' and '=' operators, '<OPERATOR>' given."`
3. The exception is thrown at the point of SQL generation, not during query construction.

Accept only working operators for tuple comparison transformation.


**Title:** Make `Driver::supports()` abstract to enforce explicit feature declaration in all drivers

**Context:**
The base `Driver::supports(string $feature): bool` method currently provides a default implementation that returns `true` only for `FEATURE_QUOTE` and `FEATURE_SAVEPOINT`, and `false` for everything else. This default is convenient but makes it possible for a new driver (or an existing one that forgets to override) to silently return incorrect values for newer features like `FEATURE_CTE`, `FEATURE_JSON`, `FEATURE_WINDOW`, etc. It also means that when a new `FEATURE_*` constant is added, drivers may inherit the default instead of explicitly declaring support, leading to subtle bugs.

**Request:**
1. Change `Driver::supports()` from a concrete method to an `abstract` method – each driver must implement it.
2. Every concrete driver (`Mysql`, `Postgres`, `Sqlite`, `Sqlserver`) must return `true` or `false` for **every** feature constant defined in `Driver`, without delegating to a parent implementation. This includes features that were previously handled by the base class (`FEATURE_QUOTE`, `FEATURE_SAVEPOINT`) – they must now be listed explicitly.
3. For unknown/undefined feature strings, the method must return `false` (this behavior is already present in the default match arm in the overridden methods).
4. Update the base class doc comment to clarify that the `FEATURE_*` constants represent the complete set of features recognized by CakePHP.

**Why:**
This change eliminates the risk of implicit defaults and ensures that any newly added `FEATURE_*` constant forces a conscious decision in every driver, rather than accidentally falling through to a generic `false`. It also makes each driver’s capability set self-documenting and reviewable in one place.


**Title:** Command composition: allow one command to execute another directly
**Status:** open
**Ref:** #12549

### Problem
Console commands today are only ever started via `CommandRunner`, which makes it difficult to compose complex workflows from smaller, reusable commands. A “deploy” command, for instance, may need to run `migrations migrate` and `cache clear`, but there is no clean way to do so from within the deploy command’s `execute()` method. Developers are forced to duplicate logic or invoke a new `CommandRunner` instance, which is heavy and not designed for programmatic use.

### Proposed solution
Add a method on the base `Command` class that allows one command to dispatch another command synchronously, using a given `ConsoleIo` and arguments array. The method should:

- Accept a **class name** (string) **or an already-instantiated `Command`** object.
- Accept the `ConsoleIo` that the caller is using (so output stays consistent).
- Accept an array of **raw CLI arguments** (e.g. `['migrate', '--quiet']`) identical to what would be passed from the shell.
- Validate that a class‑name argument resolves to an existing subclass of `Cake\Console\Command`. If an object is given, ensure it is a `Command` instance. In both invalid cases throw `InvalidArgumentException`.
- Run the target command by calling its `run()` method with the arguments and IO, so that option parsing, help output, and exit codes all behave exactly as if it were invoked from the CLI.
- **Catch any `StopException`** thrown by the inner command (including from `abort()`) and return the exception’s exit code instead of propagating it.
- Return `null` (or `0` via `CODE_SUCCESS`) on successful completion.

This would allow commands like:
```php
// Inside a deploy command
$this->executeCommand(MigrationsCommand::class, $io, ['migrate']);
$this->executeCommand(CacheClearCommand::class, $io, ['--all']);
```

### Constraints & invariants
- The called command must not inadvertently modify the caller’s state; it should run independently aside from sharing the `ConsoleIo`.
- Argument format must match what `Command::run()` expects: a plain array of strings, where options like `--quiet` and positional arguments are mixed.
- The new method should work even when called from commands that have not yet set a name, as long as a valid IO is supplied.
- It must be compatible with the existing lifecycle (initialization, option parsing, etc.) – the target command’s `initialize()` should still be called.


Add support for PostgreSQL `citext` and `interval` column types to the schema dialect.

- `citext`: case-insensitive text type; does **not** accept a length.
- `interval`: datetime interval type.
- Add corresponding type constants `TYPE_CITEXT` and `TYPE_INTERVAL` to `TableSchemaInterface`.
- When reflecting columns, handle `USER-DEFINED` types by reading the `udt_name` so that `citext` is correctly reported as `citext` instead of an unknown type.
- Ensure the column-definition SQL generation emits `CITEXT` and `INTERVAL` for the new types.

5.3 Add citext and interval types for postgres (#18711)


**Title:** `HasMany` associations with `replace` save strategy throw an exception for non-iterable empty values (`null`, `false`, `""`)

**Description:**
When saving a `HasMany` association that uses the `replace` save strategy (`$hasMany->setSaveStrategy(HasMany::SAVE_REPLACE);`), an `InvalidArgumentException` is thrown if the associated property contains `null`, `false`, or an empty string. For example:

```php
$entity->articles = null;
$sourceTable->save($entity);
```

results in:

> Could not save articles, it cannot be traversed

This contrasts with the `append` strategy, which already silently treats those values as “no associated data” and returns early without error. The behavior is also inconsistent with `BelongsToMany`, which already accepts `null`, `false`, and empty strings as empty data sets.

**Expected behavior:**
- `null`, `false`, and `""` should be treated identically to an empty array (`[]`) – as an empty set of associated records.
- When the source entity is **new** (`isNew() === true`), an empty set should be a no‑op for *any* save strategy (no unlink, no save calls).
- When the source entity is **existing** and the strategy is `replace`, an empty set should trigger the usual unlink logic to remove all existing associations (just as if an empty array had been passed).

**Reproduction case (simplified):**

```php
$articles->hasMany('Comments', ['saveStrategy' => 'replace']);
$entity = $articles->get(1);
$entity->comments = null;          // or false, or ''
$articles->save($entity);          // throws InvalidArgumentException
```

**Impact:**
Any API client sending `null` or “falsy” data for a `HasMany` field will encounter an exception, making the association unnecessarily strict. This leads to workarounds where developers must sanitize incoming data before saving, even though the intent (no associated records) is clear.

Add loose emptiness handling for `HasMany` associations.


# Add `--version` alias and shell for printing the CakePHP version

The `bin/cake` CLI runner should support `cake --version` (and the equivalent through other entry points that use `CommandRunner`). Currently there is no way to query the framework version from the command line.

## Requirements

1. Running the root command with the argument `--version` must print the current CakePHP version (as returned by `\Cake\Core\Configure::version()`) to standard output and exit cleanly.
2. The version output must be implemented in a dedicated shell class (e.g. `VersionShell` under `\Cake\Shell`) so that application developers can override the behavior by providing their own shell or modifying the command collection through `BaseApplication::console()`.
3. The `CommandRunner` must not contain hard-coded version logic. Instead, introduce a general‑purpose command alias mechanism inside `CommandRunner` that maps `--version` (and future top‑level flags) to a command name. This keeps the runner free of special‑case branches and allows the alias mapping to be extended or replaced.
4. The alias mapping must be replaceable by application code, so that a custom application can change or remove the `--version` alias if desired.
5. The change must not interfere with existing subcommand resolution and error messages; an unknown root argument like `--version` should be handled via the alias map *before* falling through to the “unknown command” error.
6. The version shell itself should be a minimal subclass of `Shell` that outputs the version string in its `main` method. No configuration or CLI options parsing is required for the version command.


Convert the `SchemaCacheShell` into separate command classes for build and clear. Keep the old CLI name `schema_cache` working so that existing deployment scripts don't break (the command classes can be named `schemacache` internally, but the shell's subcommands must remain available as `schema_cache build` and `schema_cache clear`). Support the same `--connection` option (default `'default'`) and an optional table name argument. Preserve existing behavior, including enabling the metadata cache if it was disabled, and the console output messages.

Convert SchemaCache shell into commands.


The `className` property on `Association` can currently be set only through the constructor options and read via the `className()` method. There is no public setter, and the getter does not follow the established `getX()`/`setX()` convention used by other association properties (`name`, `bindingKey`, `foreignKey`, etc.).

Add a `setClassName(string $className)` method to `Association` that:
- Allows setting the target class name after construction.
- Accepts both fully-qualified class names and short class names (e.g. `Authors`), resolving them through the same ORM conventions used at construction time.
- If the target table has already been resolved (i.e., `getTarget()` has been called), validates that the new class name resolves to the same class as the already-instantiated target table. If it does not, throws an `InvalidArgumentException` (consistent with the existing behaviour in `setName()` for name/alias mismatches).
- Returns `$this` for fluent chaining.

Rename the current `className()` getter to `getClassName()` for consistency with the rest of the API. Deprecate the old `className()` method, delegating to `getClassName()` and emitting a deprecation warning.

Add getter/setter for `Association::className()`.


Add a way to bulk-load plugins from a configuration array, supporting conditional loading (debug-only, CLI-only) and optional plugins, and have the application automatically load that array from a `plugins.php` file during bootstrap.

**Desired behavior:**
- `PluginCollection` should accept an array of plugin definitions. Each entry can be a plain plugin name string or a name → options pair.
- When the environment does not match a plugin’s conditions (`onlyDebug` when not in debug mode, or `onlyCli` when not in CLI), that plugin should be skipped without error.
- When a plugin is marked `optional` and its class/package cannot be found, the loader should silently skip it rather than throwing a `MissingPluginException`. Non-optional plugins that are missing should still throw.
- The application’s `bootstrap` phase should automatically look for a `plugins.php` file inside the application config directory, and if it exists and returns an array, pass it to the new bulk-load method. If the file does not exist or does not return an array, nothing changes.

**Config format example:**
```php
[
    'Company/TestPluginThree',
    'TestPlugin' => ['onlyDebug' => true, 'onlyCli' => true],
    'Nope' => ['optional' => true],
    'Named' => ['routes' => false, 'bootstrap' => false],
]
```

This allows applications to define their entire plugin set in one config file while honoring debug/CLI constraints and gracefully handling optional plugins without try/catch boilerplate in user code.

Allow adding plugins using a config array.


Improve `ConsoleExceptionRenderer` output to show the exception class name instead of the error code, and to dump `CakeException` attributes when debug mode is on. Make the stack trace section controllable via a `trace` option (default on) so consumers can suppress it. Also fix the fatal error test case so it no longer uses output buffering and does not emit a warning.


Finish the `Command` class dispatching pipeline.

- `Command::run` must parse arguments via `ConsoleOptionParser`, handle parsing failures gracefully, and respond to `--help`/`-h` by displaying help text (without executing the command logic).
- Help output should support `xml` format when the first positional argument is `xml`.
- After parsing, verbosity levels must be set on the `ConsoleIo` based on `--quiet` and `--verbose` flags.
- `Command::setName` must reject names that lack a space (throw `InvalidArgumentException`), and the default name should be `'cake unknown'`.
- The option parser returned by `getOptionParser` must use the command portion (after the first space) as the parser's command name, and set the root name (e.g., `'cake'`) on the parser.
- Define `CODE_SUCCESS` (0) and `CODE_ERROR` (1) constants on `Command`.
- When option parsing fails, output an error message to stderr and return `CODE_ERROR`.
- The `CommandRunner` must correctly propagate a help request (`-h`) to a command class, causing it to show help and not execute the command’s `execute` method.
- Ensure `initialize()` is called during `run()`.
- Cover edge cases: missing required arguments produce a clear error, quiet mode suppresses non‑quiet output, verbose mode includes verbose output.

Finish implementing tests for Command dispatching.


As a CakePHP application developer, I want to manage the plugin name on a controller through proper accessor methods instead of a deprecated public property, so that the property is encapsulated and its lifecycle is clear.

**Acceptance Criteria**

1. **Getter and setter for plugin**
   - A `getPlugin()` method must return the controller’s plugin name as a string.
   - A `setPlugin($name)` method must set the plugin name and return `$this` for fluent chaining.
   - The default value returned by `getPlugin()` when no plugin has been explicitly set must be an empty string (not `null`).

2. **Encapsulation of the property**
   - The existing `$plugin` property must become `protected`, so direct external access is no longer possible.
   - The `@deprecated` annotation on the `$plugin` property must be removed; the property is no longer deprecated because it is properly encapsulated.

3. **Decoupling from request**
   - The `setRequest()` method must no longer automatically assign the plugin from the request’s params. If a developer needs the plugin from the request, they should use `$request->getParam('plugin')`.

4. **Deprecation noise reduction** (Optional but desirable cleanup)
   - The deprecated `$name` and `$autoRender` properties are now backed by methods (`getName()/setName()` and `isAutoRenderEnabled()/enableAutoRender()/disableAutoRender()`). Accessing these deprecated properties should stop emitting deprecation warnings, since the recommended getters/setters are already in place.

**Why this is needed**
- The current public `$plugin` property is deprecated in favor of reading from the request, but that makes it impossible to work with a plugin context without a request object. A dedicated setter/getter allows explicit assignment independent of the request.
- Removing automatic assignment from `setRequest()` avoids confusing side-effects and makes the control flow obvious.

Added getPlugin() and setPlugin() methods for easy plugin name access.


Fix multiple bugs in `CsrfProtectionMiddleware`:

- User config must take precedence over default config when merging (current code inverts priority).
- The cookie value must be read as a string, not as the boolean result of `isset()`.
- Rename `_setCookie` to `_setToken` to clarify that it sets a CSRF token, not a generic cookie.
- `_validateAndUnsetTokenField` must return a modified request with the token field removed from the parsed body; the existing attempt to modify the request by reference does not work because the request object is immutable.
- For every unsafe HTTP method (POST, PUT, PATCH, DELETE), raise an `InvalidCsrfTokenException` when the token is missing from the request data or does not match the cookie value.
- On a GET request with no existing token cookie, generate a fresh token and set it in the response.

Adding tests for the CsrfProtectionMiddleware