Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4acd9c5
Publish support configuration files atomically
binaryfire Jul 16, 2026
5886bec
Correct DataObject dependency resolution
binaryfire Jul 16, 2026
fd39649
Isolate authentication timing state
binaryfire Jul 16, 2026
38adc51
Restore temporary framework state deterministically
binaryfire Jul 16, 2026
9caa00b
Normalize encoded HTML callables
binaryfire Jul 16, 2026
4cfc4ee
Document worker-lifetime support mutators
binaryfire Jul 16, 2026
baa7a27
Correct support numeric and encoding edge cases
binaryfire Jul 16, 2026
f849d52
Handle Fileinfo read races
binaryfire Jul 16, 2026
c86641c
Complete string helper parity
binaryfire Jul 16, 2026
8fff997
Add validated input file access
binaryfire Jul 16, 2026
d79ae15
Complete support fake parity and diagnostics
binaryfire Jul 16, 2026
7529dd5
Record intentional Laravel omissions
binaryfire Jul 16, 2026
2f178dd
Complete the Support audit record
binaryfire Jul 16, 2026
75a7568
Harden encryption key rotation
binaryfire Jul 16, 2026
5f3f670
Redact encryption secrets across framework boundaries
binaryfire Jul 16, 2026
ea28ed7
Own encryption key publication and global state
binaryfire Jul 16, 2026
13833ef
Harden strict environment list parsing
binaryfire Jul 16, 2026
f42bc66
Complete the Encryption audit record
binaryfire Jul 16, 2026
71d9c11
Correct password hashing behavior
binaryfire Jul 16, 2026
c98b7b1
Redact hashing plaintext across framework boundaries
binaryfire Jul 16, 2026
5e24ad5
Record the completed hashing audit
binaryfire Jul 16, 2026
cead6b8
Correct cookie retrieval contracts
binaryfire Jul 17, 2026
2104f22
Document selective cookie encryption
binaryfire Jul 17, 2026
30923ec
Record the completed Cookie audit
binaryfire Jul 17, 2026
52b47e0
Address support and security review findings
binaryfire Jul 17, 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

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions docs/plans/2026-07-12-framework-coroutine-state-lifecycle-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -990,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:** `support`
- **Ledger entries required for the active work:** `Isolate logging state and capture queued payloads deterministically` (`support-01`) and `Normalize framework enum identifiers at string boundaries` (`support-02`)
- **Pending revalidation carried into the active work:** callback rebinding across every direct Manager consumer; enum identifier ownership across Support managers and their consumers; later full `foundation`, `queue`, and `sentry` audits must retain the completed Log boundaries
- **Active package or work unit:** `engine`
- **Ledger entries required for the active work:** None.
- **Pending revalidation carried into the active work:** None.

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 Down Expand Up @@ -1023,6 +1023,7 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `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` |
| `filesystem-03` | `filesystem` | `encryption` (revalidation complete), `support`; later full `filesystem` audit | `Harden encryption rotation, key publication, and global lifecycle state`; finding `filesystem-03` |
| `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` |
Expand All @@ -1031,7 +1032,10 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `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` |
| `support-02` | `support` | `auth`, `broadcasting`, `bus`, `cache`, `concurrency`, `console`, `container`, `contracts`, `cookie`, `database`, `events`, `filesystem`, `foundation`, `hashing`, `horizon`, `inertia`, `jwt`, `log`, `mail`, `notifications`, `permission`, `pipeline`, `queue`, `redis`, `reverb`, `routing`, `sanctum`, `scout`, `session`, `socialite`, `telescope`, `testbench`, `translation`; later full consumer audits | `Normalize framework enum identifiers at string boundaries`; finding `support-02`; sibling findings `translation-01` and `reverb-03`; linked detail plan `2026-07-15-framework-enum-identifier-contracts.md` |
| `support-02` | `support` | `auth`, `broadcasting`, `bus`, `cache`, `concurrency`, `console`, `container`, `contracts`, `cookie`, `database`, `events`, `filesystem`, `foundation`, `hashing` (revalidation complete), `horizon`, `inertia`, `jwt`, `log`, `mail`, `notifications`, `permission`, `pipeline`, `queue`, `redis`, `reverb`, `routing`, `sanctum`, `scout`, `session`, `socialite`, `telescope`, `testbench`, `translation`; later full consumer audits | `Normalize framework enum identifiers at string boundaries`; finding `support-02`; sibling findings `translation-01` and `reverb-03`; linked detail plan `2026-07-15-framework-enum-identifier-contracts.md` |
| `auth-01` | `support`, `auth` | later full `auth` audit | `Correct Support utility boundaries and authentication timing isolation`; finding `auth-01` |
| `encryption-03` | `encryption` | `contracts` and `support` (revalidation complete), `filesystem`, `foundation`; later full `filesystem` and `foundation` audits | `Harden encryption rotation, key publication, and global lifecycle state`; finding `encryption-03` |
| `sanctum-01` | `sanctum` | `encryption`; later full `sanctum` audit | `Harden encryption rotation, key publication, and global lifecycle state`; finding `sanctum-01` |

## Package checklist

Expand Down Expand Up @@ -1065,13 +1069,13 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
- [x] `di`
- [x] `events`
- [x] `log`
- [ ] `support`
- [x] `support`

### Security and request primitives

- [ ] `encryption`
- [ ] `hashing`
- [ ] `cookie`
- [x] `encryption`
- [x] `hashing`
- [x] `cookie`

### Coroutine and resource infrastructure

Expand Down
1 change: 1 addition & 0 deletions src/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Auth for Hypervel

## Differences From Laravel

- `Auth::routes()` is intentionally omitted because Hypervel does not integrate `laravel/ui`. Register authentication routes explicitly or use Fortify.
- Password brokers are guard-declared via `auth.guards.{guard}.passwords`; `auth.defaults.passwords` and `AUTH_PASSWORD_BROKER` do not exist, and bare `Password::` calls resolve through the current guard or throw.
- `auth.defaults.provider` does not exist; `getDefaultUserProvider()` returns the provider declared by the current default guard, and `createUserProvider(null)` means no provider.
- `guest:{guard}` selects the first named guard as the request's default guard on pass-through, mirroring how `auth:{guard}` selects on success.
Expand Down
4 changes: 2 additions & 2 deletions src/auth/src/Passwords/PasswordBroker.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function __construct(
*/
public function sendResetLink(#[SensitiveParameter] array $credentials, ?Closure $callback = null): string
{
return $this->timebox->call(function () use ($credentials, $callback) {
return (clone $this->timebox)->call(function () use ($credentials, $callback) {
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
Expand Down Expand Up @@ -96,7 +96,7 @@ public function sendResetLink(#[SensitiveParameter] array $credentials, ?Closure
*/
public function reset(#[SensitiveParameter] array $credentials, Closure $callback): mixed
{
return $this->timebox->call(function ($timebox) use ($credentials, $callback) {
return (clone $this->timebox)->call(function ($timebox) use ($credentials, $callback) {
$user = $this->validateReset($credentials);

// If the responses from the validate method is not a user instance, we will
Expand Down
6 changes: 3 additions & 3 deletions src/auth/src/SessionGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public function onceUsingId(mixed $id): AuthenticatableContract|false
*/
public function validate(array $credentials = []): bool
{
return $this->timebox->call(function ($timebox) use ($credentials) {
return (clone $this->timebox)->call(function ($timebox) use ($credentials) {
$this->setContextState(
'lastAttempted',
$user = $this->provider->retrieveByCredentials($credentials)
Expand Down Expand Up @@ -354,7 +354,7 @@ protected function failedBasicResponse(): never
*/
public function attempt(array $credentials = [], bool $remember = false): bool
{
return $this->timebox->call(function ($timebox) use ($credentials, $remember) {
return (clone $this->timebox)->call(function ($timebox) use ($credentials, $remember) {
$this->fireAttemptEvent($credentials, $remember);

$this->setContextState(
Expand Down Expand Up @@ -389,7 +389,7 @@ public function attempt(array $credentials = [], bool $remember = false): bool
*/
public function attemptWhen(array $credentials = [], array|callable|null $callbacks = null, bool $remember = false): bool
{
return $this->timebox->call(function ($timebox) use ($credentials, $callbacks, $remember) {
return (clone $this->timebox)->call(function ($timebox) use ($credentials, $callbacks, $remember) {
$this->fireAttemptEvent($credentials, $remember);

$this->setContextState(
Expand Down
4 changes: 4 additions & 0 deletions src/boost/docs/data-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ $user->age;
// 39
```

Missing values use their constructor defaults. Passing `null` explicitly is distinct from omitting a nullable value.

<a name="property-name-conversion"></a>
### Property Name Conversion

Expand Down Expand Up @@ -285,6 +287,8 @@ $user->address->street;

Nested resolution works recursively for nested data object properties. If you use a union type for an auto-resolved dependency, the union should include a data object or date / time type.

You may also pass an existing nested data object instance. Auto-resolution preserves that instance instead of rebuilding it.

<a name="backed-enums"></a>
### Backed Enums

Expand Down
1 change: 1 addition & 0 deletions src/boost/docs/mail.md
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,7 @@ If you are queueing mailables for delivery in the background, you should use the

```php
Mail::assertQueued(OrderShipped::class);
Mail::assertQueuedTimes(OrderShipped::class, 2);
Mail::assertNotQueued(OrderShipped::class);
Mail::assertNothingQueued();
Mail::assertQueuedCount(3);
Expand Down
10 changes: 9 additions & 1 deletion src/boost/docs/queues.md
Original file line number Diff line number Diff line change
Expand Up @@ -3088,6 +3088,9 @@ test('orders can be shipped', function () {
// Assert a job was pushed
Queue::assertPushed(ShipOrder::class);

// Assert a job was pushed exactly once...
Queue::assertPushedOnce(ShipOrder::class);

// Assert a job was pushed twice...
Queue::assertPushedTimes(ShipOrder::class, 2);

Expand Down Expand Up @@ -3132,6 +3135,9 @@ class ExampleTest extends TestCase
// Assert a job was pushed
Queue::assertPushed(ShipOrder::class);

// Assert a job was pushed exactly once...
Queue::assertPushedOnce(ShipOrder::class);

// Assert a job was pushed twice...
Queue::assertPushedTimes(ShipOrder::class, 2);

Expand Down Expand Up @@ -3223,7 +3229,7 @@ Queue::fake()->serializeAndRestore();
<a name="testing-job-chains"></a>
### Testing Job Chains

To test job chains, you will need to utilize the `Bus` facade's faking capabilities. The `Bus` facade's `assertChained` method may be used to assert that a [chain of jobs](/docs/{{version}}/queues#job-chaining) was dispatched. The `assertChained` method accepts an array of chained jobs as its first argument:
To test job chains, you will need to utilize the `Bus` facade's faking capabilities. The `assertDispatchedOnce` method may be used to assert that a job was dispatched exactly once. The `Bus` facade's `assertChained` method may be used to assert that a [chain of jobs](/docs/{{version}}/queues#job-chaining) was dispatched. The `assertChained` method accepts an array of chained jobs as its first argument:

```php
use App\Jobs\RecordShipment;
Expand All @@ -3235,6 +3241,8 @@ Bus::fake();

// ...

Bus::assertDispatchedOnce(ShipOrder::class);

Bus::assertChained([
ShipOrder::class,
RecordShipment::class,
Expand Down
12 changes: 12 additions & 0 deletions src/boost/docs/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ By default, thanks to the `Hypervel\Cookie\Middleware\EncryptCookies` middleware
})
```

Alternatively, you may encrypt only a specific set of cookies by passing them to the `only` argument. Every cookie not listed will remain unencrypted and readable by the client:

```php
->withMiddleware(function (Middleware $middleware): void {
$middleware->encryptCookies(only: [
'session_cookie',
]);
})
```

When the `only` list is not empty, it takes precedence over the `except` list.

> [!NOTE]
> In general, cookie encryption should never be disabled, as this exposes your cookies to potential client-side data exposure and tampering.

Expand Down
48 changes: 48 additions & 0 deletions src/boost/docs/strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ If you need to clear these caches during tests, you may call `StrCache::flushSta
[Str::contains](#method-str-contains)
[Str::containsAll](#method-str-contains-all)
[Str::convertCase](#method-str-convert-case)
[Str::counted](#method-str-counted)
[Str::doesntContain](#method-str-doesnt-contain)
[Str::doesntEndWith](#method-str-doesnt-end-with)
[Str::doesntStartWith](#method-str-doesnt-start-with)
Expand Down Expand Up @@ -132,6 +133,7 @@ If you need to clear these caches during tests, you may call `StrCache::flushSta
[Str::take](#method-take)
[Str::title](#method-title-case)
[Str::toBase64](#method-str-to-base64)
[Str::trans](#method-str-trans)
[Str::transliterate](#method-str-transliterate)
[Str::trim](#method-str-trim)
[Str::ltrim](#method-str-ltrim)
Expand Down Expand Up @@ -176,6 +178,7 @@ If you need to clear these caches during tests, you may call `StrCache::flushSta
[chopEnd](#method-fluent-str-chop-end)
[contains](#method-fluent-str-contains)
[containsAll](#method-fluent-str-contains-all)
[counted](#method-fluent-str-counted)
[convertCase](#method-fluent-str-convert-case)
[decrypt](#method-fluent-str-decrypt)
[deduplicate](#method-fluent-str-deduplicate)
Expand Down Expand Up @@ -1297,6 +1300,23 @@ $password = Str::password(12);
// 'qwuar>#V|i]N'
```

<a name="method-str-counted"></a>
#### `Str::counted()` {.collection-method}

The `Str::counted` method converts a singular word string to its singular or plural form based on the given count and prefixes the result with the formatted count:

```php
use Hypervel\Support\Str;

$label = Str::counted('order', 1);

// 1 order

$label = Str::counted('order', 1000);

// 1,000 orders
```

<a name="method-str-plural"></a>
#### `Str::plural()` {.collection-method}

Expand Down Expand Up @@ -1821,6 +1841,17 @@ $base64 = Str::toBase64('Hypervel');
// SHlwZXJ2ZWw=
```

<a name="method-str-trans"></a>
#### `Str::trans()` {.collection-method}

The `Str::trans` method translates the given translation key and returns a fluent string instance:

```php
use Hypervel\Support\Str;

$message = Str::trans('messages.welcome')->upper();
```

<a name="method-str-transliterate"></a>
#### `Str::transliterate()` {.collection-method}

Expand Down Expand Up @@ -3299,6 +3330,23 @@ $closure = Str::of('foo')->pipe(function (Stringable $str) {
// 'bar'
```

<a name="method-fluent-str-counted"></a>
#### `counted` {.collection-method}

The `counted` method converts a singular word string to its singular or plural form based on the given count and prefixes the result with the formatted count:

```php
use Hypervel\Support\Str;

$label = Str::of('order')->counted(1);

// 1 order

$label = Str::of('order')->counted(1000);

// 1,000 orders
```

<a name="method-fluent-str-plural"></a>
#### `plural` {.collection-method}

Expand Down
8 changes: 8 additions & 0 deletions src/boost/docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,14 @@ $validated = $request->safe()->except(['name', 'email']);
$validated = $request->safe()->all();
```

Uploaded files may be retrieved from the validated input using the `file` method. The method returns the given default value when the input is missing or is not an uploaded file:

```php
$avatar = $request->safe()->file('avatar');

$avatar = $request->safe()->file('avatar', $default);
```

In addition, the `Hypervel\Support\ValidatedInput` instance may be iterated over and accessed like an array:

```php
Expand Down
2 changes: 1 addition & 1 deletion src/contracts/src/Cookie/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface Factory
public function make(UnitEnum|string $name, ?string $value, int $minutes = 0, ?string $path = null, ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = null): Cookie;

/**
* Create a cookie that lasts "forever" (five years).
* Create a cookie that lasts "forever" (400 days).
*/
public function forever(UnitEnum|string $name, string $value, ?string $path = null, ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = null): Cookie;

Expand Down
5 changes: 4 additions & 1 deletion src/contracts/src/Cookie/QueueingFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Hypervel\Contracts\Cookie;

use Symfony\Component\HttpFoundation\Cookie;
use UnitEnum;

interface QueueingFactory extends Factory
Expand All @@ -16,7 +17,7 @@ public function has(UnitEnum|string $key): bool;
/**
* Get a cookie value from the current request.
*/
public function get(UnitEnum|string $key, ?string $default = null): ?string;
public function get(UnitEnum|string $key, string|array|null $default = null): string|array|null;

/**
* Queue a cookie to send with the next response.
Expand All @@ -40,6 +41,8 @@ public function unqueue(UnitEnum|string $name, ?string $path = null): void;

/**
* Get the cookies which have been queued for the next request.
*
* @return array<int, Cookie>
*/
public function getQueuedCookies(): array;
}
4 changes: 3 additions & 1 deletion src/contracts/src/Encryption/Encrypter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

namespace Hypervel\Contracts\Encryption;

use SensitiveParameter;

interface Encrypter
{
/**
* Encrypt the given value.
*
* @throws \Hypervel\Contracts\Encryption\EncryptException
*/
public function encrypt(mixed $value, bool $serialize = true): string;
public function encrypt(#[SensitiveParameter] mixed $value, bool $serialize = true): string;

/**
* Decrypt the given value.
Expand Down
4 changes: 3 additions & 1 deletion src/contracts/src/Encryption/StringEncrypter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

namespace Hypervel\Contracts\Encryption;

use SensitiveParameter;

interface StringEncrypter
{
/**
* Encrypt a string without serialization.
*
* @throws \Hypervel\Contracts\Encryption\EncryptException
*/
public function encryptString(string $value): string;
public function encryptString(#[SensitiveParameter] string $value): string;

/**
* Decrypt the given string without unserialization.
Expand Down
6 changes: 4 additions & 2 deletions src/contracts/src/Hashing/Hasher.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Hypervel\Contracts\Hashing;

use SensitiveParameter;

interface Hasher
{
/**
Expand All @@ -14,12 +16,12 @@ public function info(string $hashedValue): array;
/**
* Hash the given value.
*/
public function make(string $value, array $options = []): string;
public function make(#[SensitiveParameter] string $value, array $options = []): string;

/**
* Check the given plain value against a hash.
*/
public function check(string $value, ?string $hashedValue, array $options = []): bool;
public function check(#[SensitiveParameter] string $value, ?string $hashedValue, array $options = []): bool;

/**
* Check if the given hash has been hashed using the given options.
Expand Down
Loading