* @brief Builds the rejection reply for a fanned-out op whose safety tier blocks fan-out. */
| 1405 | * @brief Builds the rejection reply for a fanned-out op whose safety tier blocks fan-out. |
| 1406 | */ |
| 1407 | static QJsonObject makeInnerOpRejection(const QString& commandName) |
| 1408 | { |
| 1409 | const auto& aiReg = AI::CommandRegistry::instance(); |
| 1410 | const auto safety = aiReg.safetyOf(commandName); |
| 1411 | |
| 1412 | QJsonObject out; |
| 1413 | out[QStringLiteral("ok")] = false; |
| 1414 | out[QStringLiteral("error")] = QStringLiteral("inner_op_blocked"); |
| 1415 | out[QStringLiteral("command")] = commandName; |
| 1416 | out[QStringLiteral("safety")] = static_cast<int>(safety); |
| 1417 | if (aiReg.isDeviceGated(commandName) && !aiReg.deviceControlAllowed()) |
| 1418 | out[QStringLiteral("hint")] = |
| 1419 | QStringLiteral("This op drives the device; enable 'Allow device control' or run it as a " |
| 1420 | "separate confirmed tool call. Batch fan-out cannot satisfy that gate."); |
| 1421 | else if (safety == AI::Safety::Blocked) |
| 1422 | out[QStringLiteral("hint")] = |
| 1423 | QStringLiteral("This op is blocked for AI safety and cannot run inside a batch."); |
| 1424 | else |
| 1425 | out[QStringLiteral("hint")] = |
| 1426 | QStringLiteral("This op needs explicit user approval and must be run as a separate tool " |
| 1427 | "call, not inside a batch fan-out."); |
| 1428 | |
| 1429 | return out; |
| 1430 | } |
| 1431 | |
| 1432 | /** |
| 1433 | * @brief Validates and forwards a project.batch payload, summarizing any per-op failures. |
no test coverage detected