Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
95 changes: 59 additions & 36 deletions lib/Controller/ApplicationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@
*
* @link https://conduction.nl
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-45
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-46
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-47
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-48
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-49
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-50
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-51
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-55
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-58
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-69
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-70
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-45
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-46
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-47
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-48
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-49
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-50
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-51
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-55
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-58
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-69
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-70
* @spec openspec/specs/openbuild-runtime/spec.md#requirement-the-runtime-must-inject-the-current-user-s-group-context
* @spec openspec/specs/openbuild-runtime/spec.md#requirement-menu-items-and-pages-must-be-filterable-by-permission
*/
Expand All @@ -53,6 +53,7 @@
use DateTimeImmutable;
use DateTimeInterface;
use OCA\OpenBuild\AppInfo\Application;
use OCA\OpenBuild\Service\AppChannelApplier;
use OCA\OpenBuild\Service\ApplicationVersionService;
use OCA\OpenBuild\Service\ManifestResolverService;
use OCA\OpenBuild\Service\PermissionResolver;
Expand Down Expand Up @@ -103,6 +104,7 @@ class ApplicationsController extends Controller
* @param IGroupManager $groupManager Group membership resolver
* @param ManifestResolverService $manifestResolver Version-aware manifest resolver (REQ-OBVR-002)
* @param PermissionResolver $permissionResolver Shared permission-grammar resolver (H1/H2 fix)
* @param AppChannelApplier $channelApplier Applies the v2 repo channels (apply-v2-channels)
* @param AuditTrailMapper|null $auditTrailMapper Optional OR audit-trail writer (null until OR loaded)
*
* @return void
Expand All @@ -117,6 +119,7 @@ public function __construct(
private readonly IGroupManager $groupManager,
private readonly ManifestResolverService $manifestResolver,
private readonly PermissionResolver $permissionResolver,
private readonly AppChannelApplier $channelApplier,
private readonly ?AuditTrailMapper $auditTrailMapper=null,
) {
parent::__construct(appName: Application::APP_ID, request: $request);
Expand Down Expand Up @@ -163,8 +166,8 @@ public function __construct(
*
* @return JSONResponse The manifest blob (carrying `runtime.user.isOwner`), or a 404 envelope when not found
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-50
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-51
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-50
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-51
* @spec openspec/changes/openbuild-admin-settings-abstraction/specs/admin-settings-owner-gating/spec.md#requirement-owner-signal-is-derived-from-existing-openbuild-primitives
*/
#[NoAdminRequired]
Expand Down Expand Up @@ -465,8 +468,8 @@ public function saveManifest(string $slug): JSONResponse
*
* @return JSONResponse 200 with manifest, or 404 when not found / not authorised.
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-69
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-70
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-69
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-70
*/
private function resolveVersionedManifestResponse(string $slug, string $versionSlug): JSONResponse
{
Expand Down Expand Up @@ -532,7 +535,7 @@ private function resolveVersionedManifestResponse(string $slug, string $versionS
* and the resolveVersionBlob() check on `applicationUuid` rejects snapshots
* that do not belong to this Application. Mirrors getManifest()'s pattern.
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-58
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-58
*/
#[NoAdminRequired]
#[NoCSRFRequired]
Expand Down Expand Up @@ -649,7 +652,7 @@ public function diffVersions(string $slug, string $from, string $to): JSONRespon
*
* @return array<string, mixed>|null Blob or null if the version is missing.
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-58
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-58
*/
private function resolveVersionBlob(string $token, array $application, string $applicationUuid): ?array
{
Expand Down Expand Up @@ -697,7 +700,7 @@ private function resolveVersionBlob(string $token, array $application, string $a
*
* @return JSONResponse|array{0: ObjectEntity|array<string, mixed>, 1: array<string, mixed>, 2: string}
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-50
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-50
*/
private function resolveApplicationBySlug(string $slug): JSONResponse|array
{
Expand Down Expand Up @@ -778,9 +781,9 @@ private function resolveApplicationBySlug(string $slug): JSONResponse|array
*
* @return JSONResponse The filtered Application list
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-46
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-47
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-48
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-46
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-47
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-48
*/
#[NoAdminRequired]
#[NoCSRFRequired]
Expand Down Expand Up @@ -862,7 +865,7 @@ public function listMine(): JSONResponse
*
* @return array{0: array<array<string,mixed>>, 1: bool} [filtered list, adminBypassUsed].
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-48
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-48
*/
private function filterApplicationsByRole(
array $results,
Expand Down Expand Up @@ -1175,8 +1178,8 @@ private function injectPermissionsSignal(array $manifest, array $applicationArra
*
* @return JSONResponse|null Null on allow, 403 JSONResponse on deny
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-45
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-47
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-45
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-47
*/
private function requirePermission(
?ObjectEntity $application,
Expand Down Expand Up @@ -1237,7 +1240,7 @@ private function requirePermission(
*
* @return void
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-49
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-49
*/
private function recordAdminBypass(?ObjectEntity $application, string $slug, string $actor): void
{
Expand Down Expand Up @@ -1300,8 +1303,8 @@ private function recordAdminBypass(?ObjectEntity $application, string $slug, str
*
* @return JSONResponse The new application's uuid + slug, or an error envelope
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-55
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-55
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
#[NoAdminRequired]
#[UserRateLimit(limit: 10, period: 3600)]
Expand Down Expand Up @@ -1454,13 +1457,22 @@ public function installFromTemplateArray(
return ['status' => $persistResult['status'], 'data' => $persistResult['error']];
}

// Apply the app-repo-format-v2 channels. Until this call existed, the four
// channels were parsed and then dropped, so an installed app arrived with
// its manifest and nothing that makes it run — and reported success.
$channels = $this->channelApplier->apply(
template: $template,
actingUserId: $ownerUid
);

return [
'status' => Http::STATUS_CREATED,
'data' => [
'uuid' => $persistResult['uuid'],
'slug' => $newSlug,
'register' => $cloneResult['register']->getSlug(),
'companionSchemas' => $cloneResult['schemaIds'],
'channels' => $channels,
],
];
}//end installFromTemplateArray()
Expand Down Expand Up @@ -1514,7 +1526,7 @@ private function resolveSharedContext(): ?array
*
* @return array<string,mixed>
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function buildClonedManifest(array $template, array $rewriteMap): array
{
Expand Down Expand Up @@ -1542,7 +1554,7 @@ private function buildClonedManifest(array $template, array $rewriteMap): array
*
* @return array{register:\OCA\OpenRegister\Db\Register,schemaIds:array<int,int>}|array{error:array<string,mixed>,status:int}
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function provisionPerAppArtifacts(
string $newSlug,
Expand Down Expand Up @@ -1584,7 +1596,18 @@ private function provisionPerAppArtifacts(
*
* @return array{uuid:string|null}|array{error:array<string,mixed>,status:int}
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-55
* Exception contract: the save is wrapped in `catch (Throwable)`, which
* deliberately covers OpenRegister's ValidationException and
* DoesNotExistException as well as anything else the write path raises — all
* of them are translated into the `clone_failed` envelope rather than leaking
* out of the controller.
*
* @throws Throwable From normaliseObject() AFTER a successful save. Deliberately
* NOT folded into the envelope: by then the Application record
* already exists, so returning `clone_failed` would tell the
* caller nothing was created when something was.
*
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-55
*/
private function persistApplication(
string $name,
Expand Down Expand Up @@ -1670,7 +1693,7 @@ private function validateCloneRequest(array $body): array
*
* @return array<int,array<string,mixed>>
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function extractCompanionSchemas(array $template): array
{
Expand All @@ -1695,7 +1718,7 @@ private function extractCompanionSchemas(array $template): array
*
* @return array<string,string>
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function buildRewriteMap(array $companions, string $newSlug): array
{
Expand All @@ -1719,7 +1742,7 @@ private function buildRewriteMap(array $companions, string $newSlug): array
*
* @return \OCA\OpenRegister\Db\Register
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function provisionPerAppRegister(string $newSlug, string $ownerUid): \OCA\OpenRegister\Db\Register
{
Expand Down Expand Up @@ -1834,7 +1857,7 @@ private function extractRegisterOwner(mixed $register): string
*
* @return array<int,int> List of created schema IDs
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function cloneCompanionSchemas(
array $companions,
Expand Down Expand Up @@ -1877,7 +1900,7 @@ private function cloneCompanionSchemas(
*
* @return mixed The rewritten node
*
* @spec openspec/changes/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
* @spec openspec/changes/archive/retrofit-2026-05-24-annotate-openbuild/tasks.md#task-56
*/
private function rewriteSchemaRefs(mixed $node, array $map): mixed
{
Expand Down
Loading
Loading