Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dc3d540
Correct explicit coroutine context targeting
binaryfire Jul 15, 2026
c141127
Cover context target and fallback isolation
binaryfire Jul 15, 2026
3a5c4d3
Document context storage ownership boundaries
binaryfire Jul 15, 2026
ee41e36
Complete the Context audit record
binaryfire Jul 15, 2026
756a0e0
Harden filesystem directory creation
binaryfire Jul 15, 2026
0007a5b
Reuse canonical metadata for aspect registration
binaryfire Jul 15, 2026
2558124
Preserve native semantics in generated AOP proxies
binaryfire Jul 15, 2026
f32ca76
Document content-addressed AOP proxies
binaryfire Jul 15, 2026
4f96e63
Record the completed DI lifecycle audit
binaryfire Jul 15, 2026
8f972f2
Record DI audit owner approval
binaryfire Jul 15, 2026
367d5c1
Forward event constructor arguments
binaryfire Jul 15, 2026
5cbb957
Correct event listener transaction semantics
binaryfire Jul 15, 2026
efa009f
Isolate queued listener execution state
binaryfire Jul 15, 2026
935c44b
Complete queued closure option forwarding
binaryfire Jul 15, 2026
990495b
Isolate mapped queue handler job state
binaryfire Jul 15, 2026
533d2a9
Complete queue backoff attribute support
binaryfire Jul 15, 2026
031ea77
Align queue lock keys with Laravel
binaryfire Jul 15, 2026
75c001b
Forward named arguments through job chains
binaryfire Jul 15, 2026
d786ead
Make coroutine event tests failure-safe
binaryfire Jul 15, 2026
5ff5365
Document current event and backoff behavior
binaryfire Jul 15, 2026
b307430
Record the completed Events audit
binaryfire Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/plans/2026-07-05-parallel-redis-runtime-isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ The scoped package-mode run also exposed shared route-file leakage. `defineCache

The scoped run also exposed a Testbench runtime clone cleanup race. Remote Testbench children with the same ParaTest token can bootstrap concurrently and both try to purge the same stale runtime copy left behind by a killed serve child. The shared `Filesystem::deleteDirectory()` behavior stays Laravel-identical; Testbench runtime-copy cleanup handles this local temp-directory race by retrying once and then accepting only the postcondition that the runtime directory is gone. If the directory still exists, the filesystem exception is rethrown.

The raw ParaTest full suite also exposed an AOP proxy source-map bug. `GenerateProxies` writes generated proxy paths back into Composer's runtime class map. Later app bootstraps then harvested that mutated map as if it still contained source paths. While the generated proxy file still existed, the bug was masked because `ProxyManager::isModified()` compared the proxy file against itself and skipped regeneration. After `cache:clear` / `optimize:clear` deleted `storage/framework/aop`, or after `package:purge-skeleton` purged the same directory from the Testbench runtime clone, a later app bootstrap tried to read the deleted proxy file as source and failed. The fix is for `GenerateProxies` to keep a worker-lifetime source class map, merge Composer's current class map into it on each proxy-generation boot while filtering generated `.proxy.php` paths, and pass explicit source file paths into `Ast` during proxy generation. Exact-rule `findFile()` resolutions must also reject generated proxy paths, because test-only `flushState()` can clear the captured source map while Composer's loader still contains a proxy entry for a previously proxied PSR-4 class. `ProxyManager` must track which source path generated each existing proxy file, because a later class-map override can point a class to a different source file whose mtime is older than the stale proxy. In that case, source-path drift must force regeneration even when the mtime check would otherwise skip it. Do not solve this by restoring Composer's loader from test cleanup; proxy generation should be self-sufficient and should not depend on per-test cleanup mutating the autoloader back into shape.
The raw ParaTest full suite also exposed an AOP proxy source-map bug. `GenerateProxies` writes generated proxy paths back into Composer's runtime class map. Later app bootstraps then harvested that mutated map as if it still contained source paths. After `cache:clear` / `optimize:clear` deleted `storage/framework/aop`, or after `package:purge-skeleton` purged the same directory from the Testbench runtime clone, a later app bootstrap tried to read the deleted proxy file as source and failed. The fix is for `GenerateProxies` to keep a worker-lifetime source class map, merge Composer's current class map into it on each proxy-generation boot while filtering generated `.proxy.php` paths, and pass explicit source paths and contents into `Ast` during proxy generation. Exact-rule `findFile()` resolutions must also reject generated proxy paths, because test-only `flushState()` can clear the captured source map while Composer's loader still contains a proxy entry for a previously proxied PSR-4 class. `ProxyManager` fingerprints the canonical source path and contents together with every generation input, so source-path changes and same-mtime content changes both invalidate a stale proxy. Do not solve this by restoring Composer's loader from production cleanup; proxy generation must remain self-sufficient. Tests that mutate Composer's loader still restore it exactly so they do not contaminate unrelated tests.

An earlier version of this plan said the full components suite should use `package:test`. That was wrong. Full-suite diagnostics proved the user's original mixed-suite concern was correct:

Expand Down

Large diffs are not rendered by default.

36 changes: 26 additions & 10 deletions docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ The following behavior was reproduced on Swoole 6.2.2 while fixing the test-suit
- `Coroutine::yield()` parks the coroutine until another path explicitly resumes it. It is not a substitute for a scheduling yield such as the production runtime's hooked `usleep(0)`.
- Native Swoole handles can already be torn down when PHP destructors run. Native channel/resource closure therefore belongs to an explicit deterministic lifecycle path, not `__destruct()`. A destructor may perform only PHP-local best-effort cleanup whose full call chain is proven not to reach a native handle.

### Verified PHP compiler behavior

PHP 8.4 changed `__FUNCTION__` and `__METHOD__` inside closures and arrow functions from the historical bare `{closure}` value to a descriptor containing the lexical parent and closure definition line, such as `{closure:App\Service::method():42}`. Nested closures compose that descriptor. A source compiler that moves a method body must preserve the original lexical name and line rather than exposing its generated helper. Revalidate this descriptor format after every PHP major or minor upgrade; a native-runtime canary must fail if PHP changes the format assumed by the generator.

## Audit principles

### 1. Verify before changing
Expand Down Expand Up @@ -986,9 +990,9 @@ An exceptionally large shared work unit may receive its own linked detail plan w

This compact index routes the completed-work history that must be consulted with the full plan after compaction. Detailed history remains in the [companion ledger](2026-07-12-framework-coroutine-state-lifecycle-audit-ledger.md).

- **Active package or work unit:** `context`
- **Ledger entries required for the active work:** `Coordinate shared container construction and complete current contextual resolution` (`container-05`, `container-06`)
- **Pending revalidation carried into the active work:** `container-05`, `container-06`
- **Active package or work unit:** `log`
- **Ledger entries required for the active work:** `Coordinate shared container construction and complete current contextual resolution` (`container-09`, `container-10`)
- **Pending revalidation carried into the active work:** `container-09`, `container-10`

Update these three lines when a package starts, completes, or gains a cross-package dependency. Name exact work-unit headings or shared finding IDs from the companion ledger; never use “see recent entries” or require a full-ledger reread.

Expand All @@ -1005,16 +1009,28 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `testbench-01` | `testbench` | `foundation`; later full `testbench` and `foundation` audits | `Restore Conditionable proxy truthiness`; shared finding `testbench-01` |
| `http-01` | `http` | `macroable`, `testing`; later full `http` and `testing` audits | `Complete Macroable callable and test-state handling`; shared finding `http-01` |
| `console-01` | `console` | `contracts`; later full `console` audit | `Preserve typed console contracts during Composer scripts`; shared finding `console-01` |
| `reflection-01` | `reflection` | `events`, `foundation`; later full `events` and `foundation` audits | `Consolidate reflection metadata and correct callable inference`; finding `reflection-01` |
| `reflection-01` | `reflection` | `events` (revalidation complete), `foundation`; later full `foundation` audit | `Consolidate reflection metadata and correct callable inference`; finding `reflection-01` |
| `reflection-02` | `reflection` | `console`, `routing`, `view`, `foundation`; later full consumer audits | `Consolidate reflection metadata and correct callable inference`; finding `reflection-02` |
| `reflection-04` | `reflection` | `di`, `support`, `queue`, `testing`; later full consumer audits | `Consolidate reflection metadata and correct callable inference`; finding `reflection-04` |
| `reflection-04` | `reflection` | `di` (revalidation complete), `support`, `queue`, `testing`; later full consumer audits | `Consolidate reflection metadata and correct callable inference`; finding `reflection-04` |
| `config-01` | `config` | `foundation`; later full `foundation` audit | `Preserve configuration identity across worker reloads`; finding `config-01` |
| `config-02` | `foundation` | `testing`, `reverb`; later full consumer audits | `Preserve configuration identity across worker reloads`; finding `config-02` |
| `container-05` | `container` | `context`; later full `context` audit | `Coordinate shared container construction and complete current contextual resolution`; finding `container-05` |
| `container-06` | `container` | `context`; later full `context` audit | `Coordinate shared container construction and complete current contextual resolution`; finding `container-06` |
| `container-05` | `container` | `context` (revalidation complete) | `Coordinate shared container construction and complete current contextual resolution`; finding `container-05` |
| `container-06` | `container` | `context` (revalidation complete) | `Coordinate shared container construction and complete current contextual resolution`; finding `container-06` |
| `container-08` | `container` | `auth`, `cache`, `log`, `routing`, `support`; later full consumer audits | `Coordinate shared container construction and complete current contextual resolution`; finding `container-08` |
| `container-09` | `auth`, `cache`, `log` | `container` (revalidation complete); later full `auth`, `cache`, and `log` audits | `Coordinate shared container construction and complete current contextual resolution`; finding `container-09` |
| `container-10` | `log` | `container` (revalidation complete); later full `log` audit | `Coordinate shared container construction and complete current contextual resolution`; finding `container-10` |
| `context-01` | `context` | `container` (revalidation complete), `foundation`; later full `foundation` audit | `Correct explicit coroutine context targeting`; finding `context-01` |
| `context-04` | `context` | `foundation`, `database`; later full consumer audits | `Correct explicit coroutine context targeting`; finding `context-04` |
| `di-02` | `di` | `foundation`, `sentry`, `telescope`; later full consumer audits | `Correct AOP proxy generation and publication`; finding `di-02` |
| `filesystem-02` | `filesystem` | `di` (revalidation complete); later full `filesystem` audit | `Correct AOP proxy generation and publication`; finding `filesystem-02` |
| `events-01` | `foundation` | `events` (revalidation complete); later full `foundation` audit | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `events-01` |
| `events-03` | `events`, `queue` | later full `queue` audit | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `events-03` |
| `events-04` | `events`, `foundation` | later full `foundation` audit | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `events-04` |
| `events-05` | `events`, `broadcasting` | later full `broadcasting` audit | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `events-05` |
| `events-06` | `events`, `foundation` | later full `foundation` audit | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `events-06` |
| `queue-11` | `queue` | `events` (revalidation complete), `broadcasting`; later full `queue` and `broadcasting` audits | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `queue-11` |
| `queue-12` | `bus`, `queue` | `events` (revalidation complete), `broadcasting`; later full `bus`, `queue`, and `broadcasting` audits | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `queue-12` |
| `foundation-01` | `foundation` | `support`; later full `foundation` and `support` audits | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `foundation-01` |

## Package checklist

Expand Down Expand Up @@ -1044,9 +1060,9 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
- [x] `reflection`
- [x] `config`
- [x] `container`
- [ ] `context`
- [ ] `di`
- [ ] `events`
- [x] `context`
- [x] `di`
- [x] `events`
- [ ] `log`
- [ ] `support`

Expand Down
4 changes: 3 additions & 1 deletion src/boost/docs/aop.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ $this->aspects(ProfileReports::class, TraceHttpRequests::class);

Hypervel generates AOP proxy classes automatically during application bootstrap. Generated proxies are written to the `storage/framework/aop` directory.

If no aspects have been registered, proxy generation does nothing. When a proxy file already exists, Hypervel compares the proxy file modification time against the original class file and regenerates the proxy when the original class is newer.
If no aspects have been registered, proxy generation does nothing. Existing proxies are reused only while their content fingerprint still matches the source path and contents, aspect rules, registered AST visitors, generator implementation, PHP parser, and PHP version. Changes to any of those inputs regenerate the affected proxy during the next application bootstrap.

Each targeted source file must declare exactly one named class, interface, trait, or enum. Nested anonymous classes are supported, but multiple named class-like declarations should be split into separate files before applying an aspect. Methods that return by reference cannot be intercepted because the aspect pipeline returns values rather than reference identities.

The `cache:clear` Artisan command removes generated AOP proxies:

Expand Down
32 changes: 11 additions & 21 deletions src/boost/docs/coroutine-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ You may pass a coroutine ID as the third argument to store a value in a specific
CoroutineContext::set('tenant_id', 123, $coroutineId);
```

An explicit coroutine ID always targets only that coroutine, regardless of whether the caller is running inside another coroutine. If the requested coroutine does not exist, `set` throws a `Hypervel\Engine\Exceptions\CoroutineDestroyedException`. In the same situation, `get` returns its default, `has` returns `false`, `forget` and `flush` do nothing, and `getContainer` returns `null`. These operations never fall back to the shared non-coroutine context store when an explicit ID is supplied.

<a name="determining-item-existence"></a>
### Determining Item Existence

Expand Down Expand Up @@ -300,31 +302,16 @@ CoroutineContext::copyFromNonCoroutine(['request_id'], $coroutineId);
<a name="copying-to-non-coroutine-context"></a>
### Copying To Non-Coroutine Context

The `copyToNonCoroutine` method copies values from a coroutine context into the non-coroutine context store:
Hypervel's test infrastructure uses the `copyToNonCoroutine` method to bridge selected state from a test coroutine into PHPUnit lifecycle code that runs outside a coroutine. You may select specific keys and, when necessary, the source coroutine:

```php
use Hypervel\Context\CoroutineContext;

use function Hypervel\Coroutine\run;

run(function () {
CoroutineContext::set('request_id', 'abc');

CoroutineContext::copyToNonCoroutine();
});
CoroutineContext::copyToNonCoroutine(['test_state'], $coroutineId);
```

You may copy only specific keys:

```php
CoroutineContext::copyToNonCoroutine(['request_id']);
```

You may pass a coroutine ID as the second argument to copy from a specific coroutine:

```php
CoroutineContext::copyToNonCoroutine(['request_id'], $coroutineId);
```
> [!WARNING]
> `copyToNonCoroutine` writes to process-global storage shared by every coroutine in the worker. It is intended only for controlled test lifecycle bridges and must not be used to propagate request state.

<a name="reading-non-coroutine-context"></a>
### Reading Non-Coroutine Context
Expand All @@ -337,12 +324,15 @@ use Hypervel\Context\CoroutineContext;
$requestId = CoroutineContext::getFromNonCoroutine('request_id');
```

You may clear specific keys from the non-coroutine context store using `clearFromNonCoroutine`:
Test infrastructure may clear its owned keys from the non-coroutine context store using `clearFromNonCoroutine`:

```php
CoroutineContext::clearFromNonCoroutine(['request_id']);
CoroutineContext::clearFromNonCoroutine(['test_state']);
```

> [!WARNING]
> `clearFromNonCoroutine` mutates process-global storage and is intended only for controlled test lifecycle cleanup.

<a name="replicable-context-values"></a>
### Replicable Context Values

Expand Down
4 changes: 3 additions & 1 deletion src/boost/docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ class SendShipmentNotification implements ShouldQueue
}
```

If you require more complex logic for determining the listeners's backoff time, you may define a `backoff` method on your listener class:
If you require more complex logic for determining the listener's backoff time, you may define a `backoff` method on your listener class:

```php
/**
Expand All @@ -817,6 +817,8 @@ public function backoff(OrderShipped $event): array
}
```

You may also declare the sequence directly on the listener using either `#[Backoff([1, 5, 10])]` or `#[Backoff(1, 5, 10)]`.

<a name="specifying-queued-listener-max-exceptions"></a>
#### Specifying Queued Listener Max Exceptions

Expand Down
2 changes: 2 additions & 0 deletions src/boost/docs/queues.md
Original file line number Diff line number Diff line change
Expand Up @@ -2805,6 +2805,8 @@ class ProcessPodcast implements ShouldQueue
}
```

The `Backoff` attribute also accepts the same sequence as separate arguments: `#[Backoff(1, 5, 10)]`.

<a name="cleaning-up-after-failed-jobs"></a>
### Cleaning Up After Failed Jobs

Expand Down
2 changes: 1 addition & 1 deletion src/broadcasting/src/BroadcastEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class BroadcastEvent implements ShouldQueue
/**
* The number of seconds to wait before retrying the job when encountering an uncaught exception.
*/
public ?int $backoff;
public array|int|null $backoff;

/**
* The maximum number of unhandled exceptions to allow before failing.
Expand Down
2 changes: 1 addition & 1 deletion src/bus/src/UniqueLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static function getKey(mixed $job): string
: ($job->uniqueId ?? '');

$jobName = method_exists($job, 'displayName')
? $job->displayName()
? hash('xxh128', $job->displayName())
: get_class($job);

// IMPORTANT: Uses Laravel's prefix for cross-framework queue interoperability.
Expand Down
2 changes: 2 additions & 0 deletions src/context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ Context for Hypervel
===

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/hypervel/context)

Ported from: https://github.com/hyperf/context
Loading
Loading