From 0cd4a4148b0451775d4b9bba06fcd2658dadef78 Mon Sep 17 00:00:00 2001 From: Kenneth Hough Date: Thu, 28 May 2026 03:03:38 -0500 Subject: [PATCH] fix(shipyard-api): sensitive toggle on Controller fails with TypeError on metadata-only PUT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third meta-controller sibling of the 4.5.1 DataModel/ModelAttribute fix. Toggling sensitive=1 on a Controller returned 200 empty + "Save failed." ControllerController::validateControllerUpdate early-returns only when $o->name === $r['name']. A metadata-only PUT of {sensitive:1} omits name, so $r['name'] is null; "SomeController" === null is false, so it fell through and passed null into the typed checkNameExistsInScope(string $name) -> TypeError -> swallowed by the global exception handler (logged as KyteError, hence the empty 200) -> update aborted before sensitive persisted. Fix: return early when name is absent — if (!isset($r['name']) || $o->name === $r['name']). The name-change path (always sends name) is unaffected. Confirmed via DEBUG_SQL on ORB: PUT Controller id=46 {sensitive:1} logged exactly this TypeError at ControllerController.php:70. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 12 ++++++++++++ src/Mvc/Controller/ControllerController.php | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e8acf7..a7ef9026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 4.5.2 + +### Bug Fix: `sensitive` toggle on a Controller silently fails (TypeError on metadata-only PUT) + +Completes the 4.5.1 fix for the third meta-controller. Toggling `sensitive=1` on a **Controller** (the detail-page toggle) returned HTTP 200 with an empty body and reverted with "Save failed." + +`ControllerController::hook_preprocess` (update) calls `validateControllerUpdate($o, $r)`, which early-returns only when `$o->name === $r['name']`. A metadata-only PUT of `{sensitive:1}` omits `name`, so `$r['name']` is `null`; `"SomeController" === null` is `false`, so it fell through and passed `null` into `checkNameExistsInScope(string $name)` → **TypeError** → caught by the global exception handler (logged as a `KyteError`, hence the empty 200) → the update aborted before `sensitive` was saved. + +Fix: `validateControllerUpdate` now returns early when `name` is absent — `if (!isset($r['name']) || $o->name === $r['name'])`. The name-change path (which always sends `name`) is unaffected. + +This is the Controller-level sibling of the 4.5.1 DataModel/ModelAttribute fixes — all three meta-controllers assumed every update carried the full record. With 4.5.2, sensitive flags can be set at controller, model, and field level. No schema change. + ## 4.5.1 ### Bug Fix: `sensitive` toggle (and any metadata-only PUT) silently fails on DataModel / ModelAttribute diff --git a/src/Mvc/Controller/ControllerController.php b/src/Mvc/Controller/ControllerController.php index 22a61722..a109db75 100644 --- a/src/Mvc/Controller/ControllerController.php +++ b/src/Mvc/Controller/ControllerController.php @@ -62,8 +62,14 @@ private function validateControllerName(string $name, int $applicationId): void */ private function validateControllerUpdate($originalController, array $updateData): void { - // Only validate if the name is actually changing - if ($originalController->name === $updateData['name']) { + // Skip when the update isn't changing the name. A metadata-only + // partial PUT — e.g. the detail page toggling `sensitive` — omits + // `name` entirely. The old `=== $updateData['name']` compared the + // existing string to null (false), so it fell through and passed + // null into checkNameExistsInScope(string $name) -> TypeError -> + // swallowed by the exception handler -> empty 200 -> save aborted + // before `sensitive` persisted. + if (!isset($updateData['name']) || $originalController->name === $updateData['name']) { return; }