release: v1.6.0 — make the queue an authenticated channel, and make BOOT_CACHE work - #124
Merged
Conversation
…and enhance retry strategies
…OOT_CACHE work Folds the outstanding Unreleased entries into 1.6.0 and adds this cycle's work. A MINOR bump, not a patch: it adds public API callers can now depend on — Kernel::withWorkerSecret(), Request::withAttributes(), SecurityVerdict::allowWithIdentity(), and a memoryLimitMb parameter on WorkerLoop::run(). The job-signature material changed shape, which would normally be breaking. It is not: verification was unreachable before this release (WorkerLoop's secret had no path in from the Kernel and was always ''), so no deployment has signed payloads in flight to migrate.
The pointer was d1b1366, which lived only in a local checkout on a detached HEAD. Nothing could resolve it: `git submodule update --init` fails for a clone and for CI, and `Kernel\Http\Request` never loads — while ResolveStage now calls withAttributes(), which exists in no pushed commit at all. Releasing against that pin would have shipped a kernel whose routing calls a method the published http package does not have. c4fe527 is the same change cherry-picked onto origin/main (AlfaCode-Team/http#7) and pushed, so the pin resolves.
hakeemRash
requested review from
Alshatri and
craftdevscommunity
as code owners
August 29, 2026 19:47
Alshatri
approved these changes
Aug 29, 2026
craftdevscommunity
approved these changes
Aug 29, 2026
hakeemRash
added a commit
that referenced
this pull request
Aug 30, 2026
…e thing (#126) Two bug fixes found while reading `src/Kernel/` end to end. Merging this fires `auto-release.yml`: it reads `## [1.6.1]` off the top of the CHANGELOG, tags `v1.6.1`, builds, publishes, and then the `homebrew` job bumps the formula's `url` + `sha256` on its own. ## Essential modules never reached a queued job `HttpPipeline` passed its essentials into `OnDemandLoader`; `WorkerLoop` built its loader with none, and `Kernel::materialize()` had no way to hand them over. A module the project declared app-wide in `proj.json` `"essentials"` was app-wide for requests and **absent from every job**. For an essential that rebinds a port per scope — tenancy rebinding `DatabasePort` — the failure is silent rather than loud: the binding still resolves, just to the wrong connection. The worker now registers essentials into every job container **and** seeds their domains into the job's graph — the same two steps `LoadStage` performs for a request, so transitive `requires[]` come with them. A job whose class the manifest does not know now also gets a container rather than the bare `CoreContainer`, since "essential" means every unit of work. An application declaring no essentials keeps its previous behaviour, that fallback included. The class→domain mapping both surfaces need moved to `DependencyGraphCalculator::domainsFor()`; a private copy in each pipeline is how they drifted apart in the first place. ## `APP_DEBUG` meant two different things in one file `ErrorStage::isDebug()` parsed the value with `FILTER_VALIDATE_BOOL` while `publicError()` compared it `=== 'true'`. So `APP_DEBUG=1` served the HTML debug page — stack trace and source excerpt — to anything sending `Accept: text/html`, while every JSON response still masked its message as "An internal error occurred.". One flag, two behaviours, and the more revealing of the two was the one that engaged. There is now one `isDebug()`, used by both. `FILTER_VALIDATE_BOOL` is the surviving parse because it is what every other kernel flag uses (`HttpPipeline::flag()`), so `1`, `on`, `yes` and `true` mean the same thing throughout. It also reads through `env()` rather than `$_ENV`/`getenv()`: the environment loader deliberately skips `putenv()`, so `getenv()` is not the source of truth for a `.env` value. **Note the direction** — with `APP_DEBUG=1` the JSON path now reveals exception messages, which is what the flag was asked for. Unset or falsy masks exactly as before. ## Verification - 359 tests, 664 assertions (was 352; +7 new). PHPStan clean on all five changed files, no baseline additions. - Both new tests were run against the **pre-fix** code. `ErrorStageDebugTest` fails on `APP_DEBUG=1 must reveal the message on the JSON path` while its three masking assertions still pass — so the fix does not loosen masking. `WorkerLoopEssentialsTest` errors on `Unknown named parameter $essentialModules`, which proves it targets the new capability rather than a changed behaviour: the old loop had no way to be told about essentials at all. - Branched from `origin/main` rather than `dev-mac`, which still carries the unsquashed 1.6.0 commits already merged as #124. Not touched: `HomebrewFormula/hkm.rb`. Its `sha256` is written by the release job after publishing, and a digest cannot exist before the release does.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Four things in the kernel were documented as working and were not. Each is a case where a declaration read as a guarantee and compiled to nothing.
The queue was not an authenticated channel
WorkerLoophas always carried a signature check, but the kernel had no way to give it a key —$signingSecretdefaulted to'', was never passed at construction, and no builder method existed. In every deployment that has ever run, it was dead code and the worker executed whatever it was handed.And the signature it would have checked covered
dataalone, leavingjobClass— the field deciding which code runs — unauthenticated. Capturing one legitimately signed envelope and swapping its class for any otherJobContractwas enough; no forgery required.Kernel::withWorkerSecret()makes the check reachable, and the material is nowjobId | jobClass | queue | maxAttempts | canonical(data). Off by default, and deliberately not falling back toAPP_KEY— that would switch verification on everywhere at once and reject every job in flight.A payload that failed verification used to return
skipped(), whichprocessWithPortthen acked — deleting the only evidence something is writing to your queue. It now dead-letters through theErrorPipeline.BOOT_CACHE never hit
build()computedbuildHash()twice, before and afterresolveEssentialModules()rewritesessentialsfrom proj.json DOMAINS into provider CLASSES. Written under one hash, read under another — so every request recompiled all ten manifests and rewrote the stamp, worse than leaving the flag off.BootStampTesttests the stamp in isolation and could not see this;KernelBootCacheTestbuilds twice through the realKernel::build()and watches the manifest inode.Also
pcntlanywhere — SIGTERM killed the worker mid-flight, including betweenhandle()returning andack(), so a job that had run its side effects came back and ran them again.retry/timeoutinmodule.jsoncompiled to nothing — every job shared one hardcoded strategy.ResolveStage10.02 -> 3.57 us;SecurityGateway4.17 -> 0.87 us.hkm runignored its documented./default — anargs.len <= 2guard fired before the resolver, which brokehkm run --devspecifically (--devis stripped before parsing, so it arrived as exactly["hkm","run"]).Depends on
AlfaCode-Team/http#7 —
ResolveStagecallsRequest::withAttributes(), which exists only there. The submodule is pinned toc4fe527from that PR's branch. If #7 is squash-merged the SHA changes and this pin must be repointed before merging.Verification
352 tests (from 312), PHPStan clean, launcher builds, and the dev server serves 200 on both routes. Benchmarks re-measured, not recalled.
Risk
Enabling job signing is a two-sided change: the
QueuePortadapter must stampJobPayload::signatureFor()atpush()time. Until it does, every payload is rejected — correct, but roll it out producer-first. No migration is needed for the algorithm change, since nothing signed before.