Skip to content

v0.5.0

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Jun 10:38
2b793a6

Release-as-a-whole: candidate MAJOR (pre-1.0 minor bump — ^0.4 → ^0.5) — ships two new rules (ForbidHttpExceptionInActionsRule, ForbidResourceWrappedInJsonResponseRule) from the Commander's review of emmie PR #481 (war-room enforcement queue #123 + #124). Both surface new errors in already-clean code wherever a consumer violates, so each consumer adopts on its own ^0.4 → ^0.5 bump PR (the ^0.{minor} caret means ^0.4 excludes 0.5.0 — tagging auto-adopts nobody). Blast radius (surveyed 2026-06-25 origin/development): ZERO violators on emmie + kendo for both rules (the #481 offender is on its branch, not merged) — the per-territory bump is expected clean save for un-merged branch work.

Added

  • ForbidHttpExceptionInActionsRule — flags throw statements inside App\Actions\* classes whose thrown expression's type is a subtype of Symfony\Component\HttpKernel\Exception\HttpExceptionInterface (the HttpException family — HttpException itself plus every subclass: NotFoundHttpException, AccessDeniedHttpException, UnprocessableEntityHttpException, …). HTTP status concerns belong to the HTTP layer; an Action that throws a 422 has reached past its boundary into transport. A uniqueness rule belongs in the FormRequest; a domain failure throws a custom domain exception the renderer maps to a status. Identifier: forbidHttpExceptionInActions.httpExceptionInAction. Doctrine: war-room §Architectural Principles — Explicit over implicit (#1) + Form Request → DTO → Action pipeline (#3). Type-aware sibling of ForbidAbortHelperRule (which bans the abort() helper family whose own message recommends throw new HttpException — correct for controllers, wrong for Actions): this rule closes the matching gap on the direct throw new HttpException(...) form inside Actions, catching subclass throws, fully-qualified throws with no use import, and typed-value throws that an import-checking arch test would miss. Illuminate\Validation\ValidationException is explicitly OUT of scope — Actions legitimately throw new ValidationException($validator) for stateful / cross-field validation that cannot live in a static FormRequest; it is not a member of the Symfony HttpException family, so the type gate never fires on it. Action-namespace gate mirrors ForbidDatabaseManagerInActionsRule (App\Actions prefix via $scope->getNamespace() + str_starts_with). Out of scope: non-App\Actions\* namespaces (controllers, FormRequests, exception renderers, middleware all legitimately raise HTTP-layer exceptions); the abort() helper family (covered by ForbidAbortHelperRule). Seed: Commander review of emmie PR #481 — CreateLocationEmailAction threw HttpException(422, …) for an "override already exists" uniqueness check (war-room enforcement queue #123). Blast radius (surveyed 2026-06-25 origin/development): ZERO raw-HttpException-in-Action on emmie + kendo (the #481 offender is on its branch, not merged) — the rule lands green and red-flags #481 at CI once enabled. Versioning: per ADR-0021 §Versioning, candidate Major bump (the rule surfaces new errors in already-clean code wherever a consumer territory has an Action throwing an HTTP-layer exception). Pre-cascade audit required per consumer at Phase-B bump (^0.4 → ^0.5); the emmie + kendo survey shows zero current violators, so the cascade is expected clean fleet-wide save for any un-merged branch work.
  • ForbidResourceWrappedInJsonResponseRule — flags response()->json($payload, …) and new JsonResponse($payload, …) inside App\Http\Controllers\* classes only when $payload's resolved type is a subtype of Illuminate\Http\Resources\Json\JsonResource. A JsonResource is already a Responsable — Laravel serializes it to a JSON response on its own; wrapping one in an explicit JSON response double-wraps the payload and discards the resource's own response shaping. Return the resource directly instead: return XxxResource::fromModel($model); (HTTP 200). Identifier: forbidResourceWrappedInJsonResponse.resourceWrapped. Doctrine: war-room §Architectural Principles — Explicit over implicit (#1) + ADR-0009 (Unified ResourceData Pattern — resources own their own response serialization). Type-awareness is mandatory: a blanket string-ban on response()->json(...) would false-positive on the overwhelmingly common legitimate sites that wrap a plain array / DTO / scalar / message envelope, and on response()->json(null, 204) (a 2026-06-25 fleet survey sized ~24 emmie + ~43 kendo response()->json/JsonResponse sites, almost all legitimate non-Resource payloads — a blanket ban would be ~67 false positives). Two AST shapes inspected: (1) MethodCall named json whose receiver is the response() helper FuncCall (AST-shape match — the helper's ResponseFactory return type is unloaded in stub-only analysis environments, mirroring how EnforceCurrentUserAttributeRule matches the auth() helper); (2) New_ of Illuminate\Http\JsonResponse (FQCN via $scope->resolveName()). Named-envelope edge (decided: EXCLUDE): a resource (or resource collection) nested under a named array key — e.g. emmie RegistrationBroadcastController:28's ['registrations' => …Resource::collect(...)] — is a deliberate response envelope, not a bare double-wrap; the first argument is an Array_ whose type is array<…>, not a JsonResource subtype, so the type gate naturally lets it through. The response()->json($resource, 201) status-override form still fires (the wrap is the violation, not the status; resource-with-non-200 is an unrelated future investigation). Controller-namespace gate mirrors ForbidEloquentMutationInControllersRule. Out of scope: non-App\Http\Controllers\* namespaces; non-JsonResource payloads; resources nested in any envelope. Seed: Commander review of emmie PR #481 — EmailController::store returned response()->json(EmailResource::fromModel($email), 201) while sibling update() correctly returned the Resource directly (war-room enforcement queue #124). Versioning: per ADR-0021 §Versioning, candidate Major bump (the rule surfaces new errors in already-clean code wherever a controller wraps a resource in an explicit JSON response). Pre-cascade audit required per consumer at Phase-B bump (^0.4 → ^0.5); the 2026-06-25 survey found ~0 current violators on emmie + kendo development (the #481 offender is on its branch).