diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..5f01cca8 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Retrofit annotation commit (opsx-annotate, 2026-05-24) +770ffed41a22f3a45e00e98a8484ca72330eefc9 diff --git a/lib/BackgroundJob/CleanupExpiredExports.php b/lib/BackgroundJob/CleanupExpiredExports.php index b4338c7d..c881ebda 100644 --- a/lib/BackgroundJob/CleanupExpiredExports.php +++ b/lib/BackgroundJob/CleanupExpiredExports.php @@ -17,6 +17,8 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-36 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -60,6 +62,8 @@ public function __construct( * @return void * * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-36 */ protected function run($argument): void { diff --git a/lib/BackgroundJob/RunExportJob.php b/lib/BackgroundJob/RunExportJob.php index 93eb15f9..efee0c61 100644 --- a/lib/BackgroundJob/RunExportJob.php +++ b/lib/BackgroundJob/RunExportJob.php @@ -18,6 +18,9 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -68,6 +71,8 @@ public function __construct( * ['jobUuid' => string]. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 */ protected function run($argument): void { @@ -126,6 +131,8 @@ private function extractJobUuid($argument): string * @param string $jobUuid Job UUID. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 */ private function executePipeline(string $jobUuid): void { @@ -161,6 +168,8 @@ private function executePipeline(string $jobUuid): void * @param string $zipPath Path to the generated ZIP. * * @return array{repoUrl?:string,pullRequestUrl?:string}|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ private function maybePush(string $jobUuid, string $zipPath): ?array { diff --git a/lib/Controller/ApplicationCreationController.php b/lib/Controller/ApplicationCreationController.php index 78786faa..91a4d27e 100644 --- a/lib/Controller/ApplicationCreationController.php +++ b/lib/Controller/ApplicationCreationController.php @@ -25,6 +25,10 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-8 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-12 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-15 */ declare(strict_types=1); @@ -78,6 +82,10 @@ public function __construct( * Returns 401 when the caller is not authenticated. * * @return JSONResponse + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-8 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-12 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-15 */ #[NoAdminRequired] public function wizard(): JSONResponse diff --git a/lib/Controller/ApplicationInsightsController.php b/lib/Controller/ApplicationInsightsController.php index dee226a5..f4d036dd 100644 --- a/lib/Controller/ApplicationInsightsController.php +++ b/lib/Controller/ApplicationInsightsController.php @@ -28,6 +28,10 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-16 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-17 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-21 */ declare(strict_types=1); @@ -81,6 +85,10 @@ public function __construct( * @param string $versionUuid ApplicationVersion UUID (path param). * * @return JSONResponse + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-16 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-17 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-21 */ #[NoAdminRequired] public function getInsights(string $appUuid, string $versionUuid): JSONResponse diff --git a/lib/Controller/ApplicationVersionsController.php b/lib/Controller/ApplicationVersionsController.php index 30e41d10..b3fe6137 100644 --- a/lib/Controller/ApplicationVersionsController.php +++ b/lib/Controller/ApplicationVersionsController.php @@ -33,6 +33,10 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-24 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 */ declare(strict_types=1); @@ -112,6 +116,8 @@ public function __construct( * @param string $slug Parent Application slug * * @return JSONResponse Versions array on 200, error envelope on miss + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-24 */ #[NoAdminRequired] public function index(string $slug): JSONResponse @@ -184,6 +190,8 @@ public function index(string $slug): JSONResponse * @param string $versionSlug ApplicationVersion slug * * @return JSONResponse The version on 200, error envelope on miss + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-24 */ #[NoAdminRequired] public function show(string $slug, string $versionSlug): JSONResponse @@ -207,6 +215,8 @@ public function show(string $slug, string $versionSlug): JSONResponse * @param string $slug Parent Application slug * * @return JSONResponse 201 with the created version, or error envelope + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-24 */ #[NoAdminRequired] public function create(string $slug): JSONResponse @@ -272,6 +282,8 @@ public function create(string $slug): JSONResponse * @param string $versionSlug ApplicationVersion slug * * @return JSONResponse 200 with the updated version, or error envelope + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-24 */ #[NoAdminRequired] public function update(string $slug, string $versionSlug): JSONResponse @@ -347,6 +359,8 @@ public function update(string $slug, string $versionSlug): JSONResponse * @param string $versionSlug ApplicationVersion slug * * @return JSONResponse 204 on success, error envelope otherwise + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 */ #[NoAdminRequired] public function destroy(string $slug, string $versionSlug): JSONResponse @@ -439,6 +453,8 @@ private function loadApplication(string $slug): ?array * @param string $versionSlug ApplicationVersion slug * * @return array|null Version record or null on miss + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-24 */ private function findVersionForApplication(string $slug, string $versionSlug): ?array { @@ -486,6 +502,8 @@ private function findVersionForApplication(string $slug, string $versionSlug): ? * @param array $roles List of role names (`owners`, `editors`, `viewers`) * * @return JSONResponse|null Null on allow, 401/403/404 envelope on deny + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 */ private function requireRole(string $slug, array $roles): ?JSONResponse { diff --git a/lib/Controller/ApplicationsController.php b/lib/Controller/ApplicationsController.php index 81f450bb..753c2baa 100644 --- a/lib/Controller/ApplicationsController.php +++ b/lib/Controller/ApplicationsController.php @@ -29,6 +29,19 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-45 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-46 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-48 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-49 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-50 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-51 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-55 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-58 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-69 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 */ declare(strict_types=1); @@ -136,6 +149,9 @@ public function __construct( * @param string $slug The virtual-app slug from the URL * * @return JSONResponse The manifest blob, or a 404 envelope when not found + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-50 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-51 */ #[NoAdminRequired] #[NoCSRFRequired] @@ -229,6 +245,9 @@ public function getManifest(string $slug): JSONResponse * @param string $versionSlug The version slug from `?_version=`. * * @return JSONResponse 200 with manifest, or 404 when not found / not authorised. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-69 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 */ private function resolveVersionedManifestResponse(string $slug, string $versionSlug): JSONResponse { @@ -271,6 +290,8 @@ private function resolveVersionedManifestResponse(string $slug, string $versionS * standard multitenancy (RegisterMapper::find + ObjectService::searchObjects), * 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-openbuilt/tasks.md#task-58 */ #[NoAdminRequired] #[NoCSRFRequired] @@ -368,6 +389,8 @@ public function diffVersions(string $slug, string $from, string $to): JSONRespon * @param string $applicationUuid Parent Application UUID for scoping. * * @return array|null Blob or null if the version is missing. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-58 */ private function resolveVersionBlob(string $token, array $application, string $applicationUuid): ?array { @@ -414,6 +437,8 @@ private function resolveVersionBlob(string $token, array $application, string $a * @param string $slug The virtual-app slug from the URL * * @return JSONResponse|array{0: ObjectEntity|array, 1: array, 2: string} + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-50 */ private function resolveApplicationBySlug(string $slug): JSONResponse|array { @@ -493,6 +518,10 @@ private function resolveApplicationBySlug(string $slug): JSONResponse|array * `permissions` — no OR envelope, no pagination metadata. * * @return JSONResponse The filtered Application list + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-46 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-48 */ #[NoAdminRequired] #[NoCSRFRequired] @@ -573,6 +602,8 @@ public function listMine(): JSONResponse * @param bool $isAdmin Whether the caller is in the Nextcloud admin group. * * @return array{0: array>, 1: bool} [filtered list, adminBypassUsed]. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-48 */ private function filterApplicationsByRole( array $results, @@ -627,6 +658,9 @@ private function filterApplicationsByRole( * @param string $slug The slug used in the audit envelope * * @return JSONResponse|null Null on allow, 403 JSONResponse on deny + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-45 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 */ private function requirePermission( ?ObjectEntity $application, @@ -686,6 +720,8 @@ private function requirePermission( * @param string $actor The bypassing user's UID * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-49 */ private function recordAdminBypass(?ObjectEntity $application, string $slug, string $actor): void { @@ -774,6 +810,8 @@ private function getUserGroupIds(IUser $user): array * Two deduplicated lists; `users` are UID values the caller's UID * should be compared against; `groups` are GID values the caller's * group memberships should be intersected with. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 */ private function collectAuthorisedGroups(array $application): array { @@ -854,6 +892,9 @@ private function classifyPrincipal(mixed $principal, array &$userSet, array &$gr * @param string $templateSlug The source template slug * * @return JSONResponse The new application's uuid + slug, or an error envelope + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-55 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ #[NoAdminRequired] public function createFromTemplate(string $templateSlug): JSONResponse @@ -1000,6 +1041,8 @@ private function resolveSharedContext(): ?array * @param array $rewriteMap Source-slug → prefixed-slug map * * @return array + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function buildClonedManifest(array $template, array $rewriteMap): array { @@ -1026,6 +1069,8 @@ private function buildClonedManifest(array $template, array $rewriteMap): array * @param array $rewriteMap Source-slug → prefixed-slug map * * @return array{register:\OCA\OpenRegister\Db\Register,schemaIds:array}|array{error:array,status:int} + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function provisionPerAppArtifacts( string $newSlug, @@ -1066,6 +1111,8 @@ private function provisionPerAppArtifacts( * @param array{register:int,templateSchema:int,applicationSchema:int} $ctx Shared context * * @return array{uuid:string|null}|array{error:array,status:int} + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-55 */ private function persistApplication( string $name, @@ -1141,6 +1188,8 @@ private function validateCloneRequest(array $body): array * @param array $template The template record * * @return array> + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function extractCompanionSchemas(array $template): array { @@ -1164,6 +1213,8 @@ private function extractCompanionSchemas(array $template): array * @param string $newSlug The new app slug used as prefix * * @return array + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function buildRewriteMap(array $companions, string $newSlug): array { @@ -1186,6 +1237,8 @@ private function buildRewriteMap(array $companions, string $newSlug): array * @param string $ownerUid The Nextcloud UID of the owner * * @return \OCA\OpenRegister\Db\Register + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function provisionPerAppRegister(string $newSlug, string $ownerUid): \OCA\OpenRegister\Db\Register { @@ -1299,6 +1352,8 @@ private function extractRegisterOwner(mixed $register): string * @param \OCA\OpenRegister\Db\Register $perAppRegister The target per-app register * * @return array List of created schema IDs + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function cloneCompanionSchemas( array $companions, @@ -1340,6 +1395,8 @@ private function cloneCompanionSchemas( * @param array $map Map of source-slug => prefixed-slug * * @return mixed The rewritten node + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-56 */ private function rewriteSchemaRefs(mixed $node, array $map): mixed { diff --git a/lib/Controller/DashboardController.php b/lib/Controller/DashboardController.php index ef418fbe..fdbe10fa 100644 --- a/lib/Controller/DashboardController.php +++ b/lib/Controller/DashboardController.php @@ -22,6 +22,8 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-52 */ declare(strict_types=1); @@ -72,6 +74,8 @@ public function __construct( * @NoCSRFRequired * * @return TemplateResponse + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-52 */ public function page(): TemplateResponse { @@ -86,6 +90,8 @@ public function page(): TemplateResponse * @NoCSRFRequired * * @return TemplateResponse + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-52 */ public function catchAll(): TemplateResponse { @@ -100,6 +106,8 @@ public function catchAll(): TemplateResponse * Empty array is published for an absent user session (defensive). * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-52 */ private function publishCurrentUserGroups(): void { diff --git a/lib/Controller/ExportsController.php b/lib/Controller/ExportsController.php index a4331051..2bc5bcd7 100644 --- a/lib/Controller/ExportsController.php +++ b/lib/Controller/ExportsController.php @@ -18,6 +18,12 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-37 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -83,6 +89,8 @@ public function __construct( * @param string $applicationSlug Slug of the source Application. * * @return bool True when the caller is allowed. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-47 */ private function isAuthorisedForApplication(string $applicationSlug): bool { @@ -210,6 +218,8 @@ private function isAuthorisedForJob(string $jobUuid): bool * @param array $body Decoded body params. * * @return JSONResponse|null JSONResponse on validation error, null on success. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-37 */ private function validateSubmitBody(array $body): ?JSONResponse { @@ -242,6 +252,8 @@ private function validateSubmitBody(array $body): ?JSONResponse * @param array $body Decoded body params. * * @return JSONResponse|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ private function validateGithubFields(array $body): ?JSONResponse { @@ -281,6 +293,8 @@ private function readStringField(array $body, string $field, string $default): s * @param string $slug Application slug. * * @return JSONResponse 202 Accepted with `{ uuid }` on success. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 */ #[NoAdminRequired] #[NoCSRFRequired] @@ -341,6 +355,8 @@ public function submit(string $slug): JSONResponse * @param string $uuid ExportJob UUID. * * @return Response 200 with the ZIP body, 410 Gone after expiry, 404 unknown. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 */ #[NoAdminRequired] #[NoCSRFRequired] diff --git a/lib/Controller/IconController.php b/lib/Controller/IconController.php index d77394af..7dc1e05f 100644 --- a/lib/Controller/IconController.php +++ b/lib/Controller/IconController.php @@ -28,6 +28,9 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-2 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-3 */ declare(strict_types=1); @@ -89,6 +92,8 @@ public function __construct( * * @NoAdminRequired * @NoCSRFRequired + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-2 */ #[NoAdminRequired] #[NoCSRFRequired] @@ -115,6 +120,8 @@ public function iconLight(string $slug): Response * * @NoAdminRequired * @NoCSRFRequired + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-3 */ #[NoAdminRequired] #[NoCSRFRequired] @@ -141,6 +148,9 @@ public function iconDark(string $slug): Response * @param bool $dark True for the dark fallback chain. * * @return Response + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-2 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-3 */ private function buildIconResponse(string $slug, bool $dark): Response { diff --git a/lib/Controller/VersionPromotionController.php b/lib/Controller/VersionPromotionController.php index e5679a21..7849c171 100644 --- a/lib/Controller/VersionPromotionController.php +++ b/lib/Controller/VersionPromotionController.php @@ -28,6 +28,11 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-59 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-65 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-67 */ declare(strict_types=1); @@ -96,6 +101,9 @@ public function __construct( * @param string $versionUuid Source ApplicationVersion UUID (path param) * * @return JSONResponse 200 + updated target on success, error envelope otherwise + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-59 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-65 */ #[NoAdminRequired] public function promote(string $appUuid, string $versionUuid): JSONResponse @@ -156,6 +164,9 @@ public function promote(string $appUuid, string $versionUuid): JSONResponse * @param Throwable $error The thrown exception * * @return JSONResponse Error envelope with the spec-defined code + status + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-67 */ private function mapExceptionToResponse(Throwable $error): JSONResponse { @@ -219,6 +230,8 @@ private function mapExceptionToResponse(Throwable $error): JSONResponse * @param VersionLockedException $error The contention exception * * @return JSONResponse + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 */ private function buildLockedResponse(VersionLockedException $error): JSONResponse { @@ -296,6 +309,8 @@ private function loadVersion(string $uuid): ?array * @return void * * @throws InsufficientPermissionException When the user lacks owner/editor role + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-65 */ private function assertEditorOrOwner(array $application, IUser $user): void { diff --git a/lib/Listener/ProductionVersionGuardListener.php b/lib/Listener/ProductionVersionGuardListener.php index eda08f38..df7fc414 100644 --- a/lib/Listener/ProductionVersionGuardListener.php +++ b/lib/Listener/ProductionVersionGuardListener.php @@ -30,6 +30,9 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-31 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-32 */ declare(strict_types=1); @@ -75,6 +78,9 @@ public function __construct( * @param Event $event Dispatched event * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-31 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-32 */ public function handle(Event $event): void { diff --git a/lib/Mcp/OpenBuiltToolProvider.php b/lib/Mcp/OpenBuiltToolProvider.php index 4ab5fb84..ba2b92c4 100644 --- a/lib/Mcp/OpenBuiltToolProvider.php +++ b/lib/Mcp/OpenBuiltToolProvider.php @@ -19,6 +19,11 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-8 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-42 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-50 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-59 + * * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 */ @@ -342,6 +347,8 @@ private function handleListApps(array $args): array * @param array $args Tool arguments (slug). * * @return array + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-50 */ private function handleGetAppManifest(array $args): array { @@ -396,6 +403,8 @@ private function handleGetAppManifest(array $args): array * @param array $args Tool arguments (slug, name, description, preset). * * @return array + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-8 */ private function handleCreateApp(array $args): array { @@ -450,6 +459,8 @@ private function handleCreateApp(array $args): array * @param array $args Tool arguments (appSlug, sourceVersionSlug, strategy). * * @return array + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-59 */ private function handlePromoteVersion(array $args): array { @@ -839,6 +850,8 @@ private function handleAddWidget(array $args): array * @param array $args Tool arguments (appSlug, versionSlug, id, label, icon, route, order). * * @return array + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-42 */ private function handleUpsertMenuItem(array $args): array { diff --git a/lib/Repair/MigrateToVersionedModel.php b/lib/Repair/MigrateToVersionedModel.php index ac017c65..e349eab4 100644 --- a/lib/Repair/MigrateToVersionedModel.php +++ b/lib/Repair/MigrateToVersionedModel.php @@ -29,6 +29,11 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-26 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-27 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-28 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-29 */ declare(strict_types=1); @@ -98,6 +103,8 @@ public function getName(): string * @param IOutput $output The output channel for progress reporting * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-26 */ public function run(IOutput $output): void { @@ -144,6 +151,8 @@ public function run(IOutput $output): void * * @throws Throwable Propagated by callers — the caller decides whether * to abort or continue + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-27 */ private function isAlreadyVersioned(): bool { @@ -189,6 +198,8 @@ private function isAlreadyVersioned(): bool * Fetch every Application row in the `openbuilt` register. * * @return array> + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-26 */ private function enumerateApplications(): array { @@ -241,6 +252,9 @@ private function enumerateApplications(): array * @param IOutput $output Output channel for progress * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-28 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-29 */ private function migrateOne(array $application, IOutput $output): void { diff --git a/lib/Repair/PopulateApplicationPermissions.php b/lib/Repair/PopulateApplicationPermissions.php index 1a70f1e4..d442f40d 100644 --- a/lib/Repair/PopulateApplicationPermissions.php +++ b/lib/Repair/PopulateApplicationPermissions.php @@ -28,6 +28,8 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-30 */ declare(strict_types=1); @@ -80,6 +82,8 @@ public function getName(): string * @param IOutput $output The output interface for progress reporting * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-30 */ public function run(IOutput $output): void { @@ -151,6 +155,8 @@ public function run(IOutput $output): void * @param array $application The Application data * * @return bool True when the Application should be patched + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-30 */ private function needsMigration(array $application): bool { diff --git a/lib/Repair/SeedApplicationTemplates.php b/lib/Repair/SeedApplicationTemplates.php index 6a7fb1e2..4be58fe8 100644 --- a/lib/Repair/SeedApplicationTemplates.php +++ b/lib/Repair/SeedApplicationTemplates.php @@ -25,6 +25,9 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-54 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-57 */ declare(strict_types=1); @@ -101,6 +104,8 @@ public function getName(): string * @param IOutput $output The output interface for progress reporting * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-54 */ public function run(IOutput $output): void { @@ -165,6 +170,8 @@ public function run(IOutput $output): void * @return void * * @throws RuntimeException When a required field is missing or empty. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-57 */ private function validateFixture(array $data, string $slug): void { diff --git a/lib/Service/AppNavigationService.php b/lib/Service/AppNavigationService.php index d998bc3d..0cb30dbe 100644 --- a/lib/Service/AppNavigationService.php +++ b/lib/Service/AppNavigationService.php @@ -30,6 +30,11 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-4 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-5 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-6 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-7 */ declare(strict_types=1); @@ -110,6 +115,8 @@ public function __construct( * @param INavigationManager $nav The Nextcloud navigation manager. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-4 */ public function registerNavEntries(INavigationManager $nav): void { @@ -201,6 +208,9 @@ function () use ( * @param IGroupManager $groupManager The group manager. * * @return bool True when the entry should be visible. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-5 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-6 */ public function isVisibleForCurrentUser( array $permissions, @@ -241,6 +251,8 @@ public function isVisibleForCurrentUser( * @param array $permissions The Application's permissions block. * * @return array All principals from owners + editors + viewers. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-5 */ private function flattenPermissions(array $permissions): array { @@ -270,6 +282,8 @@ private function flattenPermissions(array $permissions): array * @param array $userGroups The calling user's group IDs. * * @return bool True when a group match is found. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-5 */ private function principalsMatchGroups(array $principals, array $userGroups): bool { @@ -303,6 +317,9 @@ private function principalsMatchGroups(array $principals, array $userGroups): bo * @return array> List of normalised Application arrays. * * @throws \Throwable When the OR query fails. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-4 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-7 */ private function getPublishedApplications(): array { diff --git a/lib/Service/ApplicationCreationService.php b/lib/Service/ApplicationCreationService.php index e6f7ae3e..08bd7541 100644 --- a/lib/Service/ApplicationCreationService.php +++ b/lib/Service/ApplicationCreationService.php @@ -36,6 +36,14 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-9 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-10 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-11 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-12 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-13 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-14 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-15 */ declare(strict_types=1); @@ -121,6 +129,11 @@ public function __construct( * * @throws WizardCreationException On validation failure (failedAtStep=validate) * or on any mid-flight creation failure (with rollback) + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-12 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-13 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-14 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-15 */ public function createApplication(array $payload): string { @@ -406,6 +419,9 @@ public function createApplication(array $payload): string * @return void * * @throws WizardCreationException With failedAtStep=validate on any failure + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-10 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-11 */ private function validatePayload(array $payload): void { @@ -504,6 +520,9 @@ private function validatePayload(array $payload): void * @param array $payload The wizard POST payload * * @return array> Version definitions [{name, slug}, ...] + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-9 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-11 */ public function resolveVersionChain(array $payload): array { @@ -540,6 +559,8 @@ public function resolveVersionChain(array $payload): array * @param string $slug The slug to check * * @return bool True when a conflicting row exists + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-10 */ private function appSlugExists(string $slug): bool { @@ -584,6 +605,8 @@ private function appSlugExists(string $slug): bool * @return void * * @throws Throwable When register creation or schema seeding fails + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-13 */ private function provisionRegister( string $registerSlug, @@ -638,6 +661,8 @@ private function provisionRegister( * @param string $registerSlug The OR register slug to drop * * @return bool True on success, false on failure + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-12 */ private function deleteRegister(string $registerSlug): bool { @@ -668,6 +693,8 @@ private function deleteRegister(string $registerSlug): bool * @param array $orphaned Accumulates resources that could not be cleaned (by ref) * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-12 */ private function rollback(array $state, array &$orphaned): void { @@ -719,6 +746,8 @@ private function rollback(array $state, array &$orphaned): void * @return array The parsed manifest blob * * @throws WizardCreationException When the fixture cannot be read or decoded + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-14 */ private function loadDefaultManifest(): array { @@ -761,6 +790,8 @@ private function loadDefaultManifest(): array * @return array> The parsed schema blobs * * @throws WizardCreationException When the fixture cannot be read or decoded + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-13 */ private function loadDefaultSchemas(): array { @@ -807,6 +838,8 @@ private function loadDefaultSchemas(): array * @param string $registerSlug The per-version register slug * * @return array The manifest with the token substituted + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-13 */ public function substituteRegisterSlug(array $manifest, string $registerSlug): array { @@ -833,6 +866,8 @@ public function substituteRegisterSlug(array $manifest, string $registerSlug): a * @param string $schemaSlugPrefix Namespaced prefix for schema slugs (e.g. `permit-flow-development-`) * * @return array The manifest with tokens substituted + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-14 */ public function substituteVersionContext( array $manifest, diff --git a/lib/Service/ApplicationInsightsService.php b/lib/Service/ApplicationInsightsService.php index 51313e42..fdb7ba48 100644 --- a/lib/Service/ApplicationInsightsService.php +++ b/lib/Service/ApplicationInsightsService.php @@ -42,6 +42,12 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-16 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-17 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-18 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-19 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-20 */ declare(strict_types=1); @@ -137,6 +143,8 @@ public function __construct( * @param IUser|null $caller The authenticated user, or null for unauthenticated. * * @return array{0: array, 1: array}|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-17 */ public function requireAuthorisedCaller( string $appUuid, @@ -184,6 +192,10 @@ public function requireAuthorisedCaller( * @param IUser|null $caller The authenticated user, or null for unauthenticated. * * @return array|null Insights payload `{kpis, activity}` or null on 404. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-16 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-19 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-20 */ public function computeInsights( string $appUuid, @@ -250,6 +262,8 @@ public function computeInsights( * @param string $registerSlug The version's per-version register slug. * * @return array Unique schema IDs (string form — OR stores audit schema column as VARCHAR). + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-18 */ public function deriveSchemaIds(?array $manifest, string $registerSlug): array { @@ -287,6 +301,8 @@ public function deriveSchemaIds(?array $manifest, string $registerSlug): array * @param string $registerSlug The version's per-version register slug. * * @return string|null The schema ID, or null when the page does not match. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-18 */ private function extractSchemaIdForRegister(mixed $page, string $registerSlug): ?string { @@ -324,6 +340,8 @@ private function extractSchemaIdForRegister(mixed $page, string $registerSlug): * @param IUser|null $caller The authenticated user. * * @return bool True when authorised, false otherwise. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-17 */ private function isAuthorised(array $application, array $version, ?IUser $caller): bool { @@ -528,6 +546,8 @@ private function extractManifest(array $version): ?array * @param int $hours Window hours. * * @return int Distinct actor count, or 0 when the aggregation API is unavailable. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-19 */ private function safeDistinctActorCount(array $schemaIds, int $hours): int { @@ -565,6 +585,8 @@ private function safeDistinctActorCount(array $schemaIds, int $hours): int * @param string $registerSlug The version's register slug. * * @return int Total object count across the schema-set. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-19 */ private function countObjects(array $schemaIds, string $registerSlug): int { @@ -610,6 +632,8 @@ private function countObjects(array $schemaIds, string $registerSlug): int * @return int File count. * * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-19 */ private function countAttachedFiles(string $registerSlug, array $schemaIds): int { @@ -655,6 +679,8 @@ private function countAttachedFiles(string $registerSlug, array $schemaIds): int * @param int $hours Window hours. * * @return int Audit-event count. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-19 */ private function countAuditEvents(array $schemaIds, int $hours): int { @@ -757,6 +783,8 @@ private function sumChartSeries(mixed $chart): int * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @psalm-suppress UnusedParam + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-20 */ private function buildActivityTimeline(array $schemaIds, int $hours, string $registerSlug): array { diff --git a/lib/Service/ApplicationVersionService.php b/lib/Service/ApplicationVersionService.php index 03336d47..1be386a6 100644 --- a/lib/Service/ApplicationVersionService.php +++ b/lib/Service/ApplicationVersionService.php @@ -34,6 +34,11 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-22 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-23 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-31 */ declare(strict_types=1); @@ -145,6 +150,8 @@ public function __construct( * @return string Canonical JSON * * @throws \JsonException When the structure contains non-encodable values + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-22 */ public function canonicaliseManifest(array $manifest): string { @@ -162,6 +169,8 @@ public function canonicaliseManifest(array $manifest): string * @return string 64-char lowercase hexadecimal digest * * @throws \JsonException When the manifest contains non-encodable values + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-22 */ public function hashManifest(array $manifest): string { @@ -177,6 +186,8 @@ public function hashManifest(array $manifest): string * @return string The patch-bumped semver * * @throws RuntimeException When the input is not a recognisable semver + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-22 */ public function bumpPatch(string $semver): string { @@ -213,6 +224,8 @@ public function bumpPatch(string $semver): string * @return array The mutated `$next` array * * @throws \JsonException When the manifest cannot be canonicalised + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-22 */ public function onSave(?array $current, array $next): array { @@ -267,6 +280,8 @@ public function onSave(?array $current, array $next): array * @return void * * @throws RuntimeException When a cycle is detected or the cap is exceeded + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-23 */ public function guardNoCycle(string $currentUuid, ?string $proposedTargetUuid): void { @@ -321,6 +336,8 @@ public function guardNoCycle(string $currentUuid, ?string $proposedTargetUuid): * @return void * * @throws RuntimeException When the back-reference does not point at the parent + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-31 */ public function guardProductionVersionOwnership(string $applicationUuid, string $proposedVersionUuid): void { @@ -385,6 +402,8 @@ public function guardProductionVersionOwnership(string $applicationUuid, string * * @throws RuntimeException On unknown strategy, missing version, or * production-version refusal + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 */ public function deleteVersion(string $versionUuid, string $strategy): void { @@ -437,6 +456,8 @@ public function deleteVersion(string $versionUuid, string $strategy): void * @return void * * @throws RuntimeException When the strategy is not recognised + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 */ private function assertValidStrategy(string $strategy): void { @@ -460,6 +481,8 @@ private function assertValidStrategy(string $strategy): void * @return void * * @throws RuntimeException When the row is the parent's productionVersion + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 */ private function assertNotProductionVersion(array $versionData, string $versionUuid): void { @@ -500,6 +523,8 @@ private function assertNotProductionVersion(array $versionData, string $versionU * @param string $versionUuid The owning ApplicationVersion UUID (diagnostics) * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 */ private function dropPerVersionRegister(string $registerSlug, string $versionUuid): void { @@ -549,6 +574,8 @@ private function dropPerVersionRegister(string $registerSlug, string $versionUui * @param string $versionUuid The owning ApplicationVersion UUID (diagnostics) * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-25 */ private function flagRegisterOrphaned(string $registerSlug, string $versionUuid): void { diff --git a/lib/Service/ExportJobService.php b/lib/Service/ExportJobService.php index 25572941..df14efad 100644 --- a/lib/Service/ExportJobService.php +++ b/lib/Service/ExportJobService.php @@ -18,6 +18,12 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-37 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-38 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -70,6 +76,8 @@ public function __construct( * @return string Job UUID (UUIDv4). * * @throws \InvalidArgumentException When required fields are missing. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-33 */ public function queue( string $applicationSlug, @@ -140,6 +148,8 @@ public function queue( * @param array $job Sanitised job record. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-37 */ public function persistJob(array $job): void { @@ -181,6 +191,8 @@ public function persistJob(array $job): void * * @return bool True when the transition fired, false when OR's * lifecycle engine is not available (gap recorded). + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-38 */ public function transitionJob( string $jobUuid, @@ -241,6 +253,8 @@ public function transitionJob( * downloadUrl, downloadExpiresAt, …). * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-37 */ public function mergeJobFields(string $jobUuid, array $fields): void { @@ -287,6 +301,8 @@ public function mergeJobFields(string $jobUuid, array $fields): void * @param string $uuid ExportJob UUID. * * @return array{path:string,expired:bool}|null Resolution result. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 */ public function resolveDownload(string $uuid): ?array { @@ -309,6 +325,8 @@ public function resolveDownload(string $uuid): ?array * @param string $jobUuid Job UUID. * * @return string|null PAT or null when none was stored. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ public function fetchPat(string $jobUuid): ?string { @@ -326,6 +344,8 @@ public function fetchPat(string $jobUuid): ?string * @param string $jobUuid Job UUID. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ public function clearPat(string $jobUuid): void { @@ -342,6 +362,8 @@ public function clearPat(string $jobUuid): void * @param string $jobUuid Job UUID. * * @return string Credentials key. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ public function credentialKey(string $jobUuid): string { diff --git a/lib/Service/ExportService.php b/lib/Service/ExportService.php index f45a64af..fabcfef8 100644 --- a/lib/Service/ExportService.php +++ b/lib/Service/ExportService.php @@ -21,6 +21,10 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-41 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -103,6 +107,8 @@ public function __construct( * @return string Absolute (local) path to the produced ZIP. * * @throws RuntimeException When packaging fails. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 */ public function generateAppZip( string $applicationUuid, @@ -136,6 +142,8 @@ public function generateAppZip( * @return string Local path to the ZIP file. * * @throws RuntimeException When ZIP creation fails. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 */ public function packageZip(string $sourceDir, string $jobUuid): string { @@ -181,6 +189,8 @@ public function packageZip(string $sourceDir, string $jobUuid): string * @param string $baseDir Directory to walk. * * @return array Sorted relative file paths. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-41 */ public function listFilesSorted(string $baseDir): array { @@ -212,6 +222,8 @@ public function listFilesSorted(string $baseDir): array * @param array $context Placeholder context. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 */ public function resolvePlaceholders(string $rootDir, array $context): void { @@ -272,6 +284,8 @@ public function isBinary(string $path): bool * @param string $dest Scratch dir. * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 */ public function copyTemplate(string $source, string $dest): void { @@ -349,6 +363,8 @@ public function prepareScratchDir(string $jobUuid): string * @param string $name Subdir name under appdata's openbuilt area. * * @return string Absolute local path. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-35 */ public function getOrCreateAppDataDir(string $name): string { diff --git a/lib/Service/GitHubPushService.php b/lib/Service/GitHubPushService.php index efe8bac0..5920fd11 100644 --- a/lib/Service/GitHubPushService.php +++ b/lib/Service/GitHubPushService.php @@ -22,6 +22,8 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -58,6 +60,8 @@ public function __construct( * @param string $pat GitHub PAT — method-scoped, never persisted. * * @return array{repoUrl:string,pullRequestUrl:string} URLs of the created repo + PR. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ public function push(string $jobUuid, string $treeDir, string $pat): array { @@ -85,6 +89,8 @@ public function push(string $jobUuid, string $treeDir, string $pat): array * @param string $pat GitHub PAT — method-scoped. * * @return string Default branch name. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-34 */ public function resolveDefaultBranch(string $org, string $pat): string { diff --git a/lib/Service/IconService.php b/lib/Service/IconService.php index 37a488ec..4fb496ff 100644 --- a/lib/Service/IconService.php +++ b/lib/Service/IconService.php @@ -26,6 +26,10 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-1 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-2 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-3 */ declare(strict_types=1); @@ -106,6 +110,9 @@ public function __construct( * stream is null only when * no filesystem fallback * exists (practically never). + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-2 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-3 */ public function getIconStream(string $slug, bool $dark): array { @@ -127,6 +134,8 @@ public function getIconStream(string $slug, bool $dark): array * @param string $slug The Application slug. * * @return array|null Application data array, or null. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-1 */ private function fetchApplication(string $slug): ?array { @@ -165,6 +174,8 @@ private function fetchApplication(string $slug): ?array * @param array|null $application Application data or null. * * @return array{stream: resource|null, mimeType: string} + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-2 */ private function resolveIconLight(?array $application): array { @@ -195,6 +206,8 @@ private function resolveIconLight(?array $application): array * @param array|null $application Application data or null. * * @return array{stream: resource|null, mimeType: string} + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-3 */ private function resolveIconDark(?array $application): array { diff --git a/lib/Service/ManifestResolverService.php b/lib/Service/ManifestResolverService.php index 2c5a5983..329ad549 100644 --- a/lib/Service/ManifestResolverService.php +++ b/lib/Service/ManifestResolverService.php @@ -34,6 +34,9 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-71 */ declare(strict_types=1); @@ -93,6 +96,9 @@ public function __construct( * @param IUser|null $caller The authenticated user, or null for unauthenticated. * * @return array|null The manifest payload, or null → caller maps to 404. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-71 */ public function resolve(string $appSlug, ?string $versionSlug, ?IUser $caller): ?array { @@ -163,6 +169,8 @@ public function resolve(string $appSlug, ?string $versionSlug, ?IUser $caller): * @param string $versionSlug The version slug (for logging). * * @return bool True when access is denied; false when access is allowed. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-71 */ private function checkNonProductionAccess( array $application, @@ -212,6 +220,8 @@ private function checkNonProductionAccess( * @param string $appSlug The application slug. * * @return array|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 */ private function findApplicationBySlug(string $appSlug): ?array { @@ -255,6 +265,8 @@ private function findApplicationBySlug(string $appSlug): ?array * @param string $appSlug The app slug (for logging). * * @return array|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 */ private function resolveProductionManifest(array $application, string $appSlug): ?array { @@ -306,6 +318,8 @@ private function resolveProductionManifest(array $application, string $appSlug): * @param string $versionSlug The requested version slug. * * @return array|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-70 */ private function findVersionBySlug(array $application, string $versionSlug): ?array { @@ -492,6 +506,8 @@ private function extractProductionVersionUuid(array $application): string * @param IUser|null $caller The authenticated user. * * @return bool True when the caller is an editor or owner; false otherwise. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-71 */ private function isCallerAuthorised(array $application, ?IUser $caller): bool { diff --git a/lib/Service/PlaceholderResolver.php b/lib/Service/PlaceholderResolver.php index 4bb99dda..b457e45c 100644 --- a/lib/Service/PlaceholderResolver.php +++ b/lib/Service/PlaceholderResolver.php @@ -18,6 +18,8 @@ * * @link https://conduction.nl * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 + * * @SPDX-License-Identifier: EUPL-1.2 * @SPDX-FileCopyrightText: 2026 Conduction B.V. */ @@ -45,6 +47,8 @@ final class PlaceholderResolver * authorName, authorEmail, license. * * @return array Map of search → replace. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 */ public function buildMap(array $context): array { @@ -76,6 +80,8 @@ public function buildMap(array $context): array * @param array $map Placeholder map from buildMap(). * * @return string Resolved content. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-40 */ public function resolve(string $content, array $map): string { diff --git a/lib/Service/SlugValidator.php b/lib/Service/SlugValidator.php index 2e3a86cf..6d97bf37 100644 --- a/lib/Service/SlugValidator.php +++ b/lib/Service/SlugValidator.php @@ -27,6 +27,9 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-10 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-11 */ declare(strict_types=1); @@ -64,6 +67,8 @@ class SlugValidator * @param string $slug The proposed slug * * @return array Empty array on success; error array on failure + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-10 */ public function validateAppSlug(string $slug): array { @@ -103,6 +108,8 @@ public function validateAppSlug(string $slug): array * @param string $slug The proposed version slug * * @return array Empty array on success; error array on failure + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-10 */ public function validateVersionSlug(string $slug): array { @@ -142,6 +149,8 @@ public function validateVersionSlug(string $slug): array * @param array $slugs Ordered list of version slugs for one chain * * @return array Empty array on success; error array identifying the collision + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-11 */ public function validateChainSlugs(array $slugs): array { diff --git a/lib/Service/VersionPromotionService.php b/lib/Service/VersionPromotionService.php index ad7a779f..1b7414ca 100644 --- a/lib/Service/VersionPromotionService.php +++ b/lib/Service/VersionPromotionService.php @@ -38,6 +38,16 @@ * @version GIT: * * @link https://conduction.nl + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-59 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-60 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-61 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-62 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-63 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-66 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-67 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-68 */ declare(strict_types=1); @@ -140,6 +150,8 @@ public function __construct( * * @return string One of {@see self::STRATEGY_MIGRATE_EXISTING_DATA}, * {@see self::STRATEGY_START_WITH_SOURCE_DATA}. + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-68 */ public static function defaultStrategyFor(array $application, array $target): string { @@ -171,6 +183,8 @@ public static function defaultStrategyFor(array $application, array $target): st * @throws InvalidStrategyException When the strategy is missing or unknown * @throws VersionLockedException When OR's lock is held by another caller * @throws PromotionFailedException When the strategy branch fails midway + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-59 */ public function promote(array $source, string $strategy): array { @@ -208,6 +222,10 @@ public function promote(array $source, string $strategy): array * @return array The updated target row * * @throws PromotionFailedException On any failure inside the branch + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-60 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-61 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-62 */ private function runStrategy(string $strategy, array $source, array $target): array { @@ -241,6 +259,8 @@ private function runStrategy(string $strategy, array $source, array $target): ar * @param array $target Target ApplicationVersion * * @return array The updated target row + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-60 */ private function runStartWithSourceData(array $source, array $target): array { @@ -260,6 +280,8 @@ private function runStartWithSourceData(array $source, array $target): array * @param array $target Target ApplicationVersion * * @return array The updated target row + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-61 */ private function runMigrateExistingData(array $source, array $target): array { @@ -276,6 +298,8 @@ private function runMigrateExistingData(array $source, array $target): array * @param array $target Target ApplicationVersion * * @return array The updated target row + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-62 */ private function runEmptyStart(array $source, array $target): array { @@ -298,6 +322,8 @@ private function runEmptyStart(array $source, array $target): array * @param array $target Target ApplicationVersion * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-63 */ private function forwardSchemaSetToOR(array $source, array $target): void { @@ -341,6 +367,9 @@ private function forwardSchemaSetToOR(array $source, array $target): void * @param array $target Target ApplicationVersion * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-60 + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-62 */ private function wipeTargetRegister(array $target): void { @@ -383,6 +412,8 @@ private function wipeTargetRegister(array $target): void * @param array $target Target ApplicationVersion * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-60 */ private function copyRowsFromSource(array $source, array $target): void { @@ -431,6 +462,8 @@ private function copyRowsFromSource(array $source, array $target): void * @param array $target Target ApplicationVersion * * @return array The persisted target row + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-66 */ private function applyManifestAndSemver(array $source, array $target): array { @@ -465,6 +498,8 @@ private function applyManifestAndSemver(array $source, array $target): array * @return never * * @throws PromotionFailedException Always + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-67 */ private function handlePromotionFailure(string $targetUuid, string $strategy, Throwable $error): never { @@ -523,6 +558,8 @@ private function handlePromotionFailure(string $targetUuid, string $strategy, Th * @return void * * @throws VersionLockedException When OR's lockObject reports contention + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 */ private function acquireLock(string $targetUuid): void { @@ -566,6 +603,8 @@ private function acquireLock(string $targetUuid): void * @param string $targetUuid UUID of the target row * * @return void + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 */ private function releaseLock(string $targetUuid): void { @@ -586,6 +625,8 @@ private function releaseLock(string $targetUuid): void * @param string $targetUuid UUID to look up * * @return array|null + * + * @spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-64 */ private function callGetLockInfo(string $targetUuid): ?array { diff --git a/openspec/changes/archive/retrofit-2026-05-24-annotate-openbuilt/proposal.md b/openspec/changes/archive/retrofit-2026-05-24-annotate-openbuilt/proposal.md new file mode 100644 index 00000000..6ac78fc2 --- /dev/null +++ b/openspec/changes/archive/retrofit-2026-05-24-annotate-openbuilt/proposal.md @@ -0,0 +1,7 @@ +# Retrofit — annotate openbuilt against existing specs + +Retroactive annotation of 142 methods across 27 files against 71 REQs in 16 capabilities. No code logic changes. No spec deltas (all REQs already exist in `openspec/specs/`). + +Source: `openspec/coverage-report.md` generated 2026-05-24 (Bucket 1 only). 43 `needs_review: true` entries skipped per the opsx-annotate playbook. + +See [retrofit playbook](../../../../hydra/.github/docs/claude/retrofit.md). diff --git a/openspec/changes/archive/retrofit-2026-05-24-annotate-openbuilt/tasks.md b/openspec/changes/archive/retrofit-2026-05-24-annotate-openbuilt/tasks.md new file mode 100644 index 00000000..c6445abe --- /dev/null +++ b/openspec/changes/archive/retrofit-2026-05-24-annotate-openbuilt/tasks.md @@ -0,0 +1,73 @@ +# Tasks + +- [x] task-1: app-icon-management#REQ-OBICON-001 — Icon fields on Application schema (top-level) (retroactive annotation) +- [x] task-2: app-icon-management#REQ-OBICON-002 — Icon-serving endpoint (light) (retroactive annotation) +- [x] task-3: app-icon-management#REQ-OBICON-003 — Icon-serving endpoint (dark) (retroactive annotation) +- [x] task-4: app-nav-entries#REQ-OBNAV-001 — Dynamic per-app top-bar entry for each published Application (retroactive annotation) +- [x] task-5: app-nav-entries#REQ-OBNAV-002 — Nav entry gated by permissions RBAC (retroactive annotation) +- [x] task-6: app-nav-entries#REQ-OBNAV-003 — group-wildcard nav-entry visibility SHALL apply to all signed-in users (retroactive annotation) +- [x] task-7: app-nav-entries#REQ-OBNAV-004 — Nav entry list is re-evaluated per request without writeback (retroactive annotation) +- [x] task-8: application-creation-wizard#REQ-OBWIZ-001 — Wizard replaces the legacy Add-Application entry point (retroactive annotation) +- [x] task-9: application-creation-wizard#REQ-OBWIZ-002 — Four-step wizard shape (retroactive annotation) +- [x] task-10: application-creation-wizard#REQ-OBWIZ-005 — Slug derivation + leading-underscore rejection (retroactive annotation) +- [x] task-11: application-creation-wizard#REQ-OBWIZ-006 — No duplicate version slugs within a chain (retroactive annotation) +- [x] task-12: application-creation-wizard#REQ-OBWIZ-007 — Atomic creation with full rollback on failure (retroactive annotation) +- [x] task-13: application-creation-wizard#REQ-OBWIZ-008 — Per-version registers + seed schema set (retroactive annotation) +- [x] task-14: application-creation-wizard#REQ-OBWIZ-009 — Initial manifest, semver, status per version (retroactive annotation) +- [x] task-15: application-creation-wizard#REQ-OBWIZ-010 — Caller becomes sole owner (retroactive annotation) +- [x] task-16: application-insights#REQ-OBAI-001 — Insights endpoint returns KPIs and activity timeline for a version (retroactive annotation) +- [x] task-17: application-insights#REQ-OBAI-002 — Auth gate mirrors openbuilt-version-routing (retroactive annotation) +- [x] task-18: application-insights#REQ-OBAI-003 — Schema-set walk over the version's manifest.pages[].config (retroactive annotation) +- [x] task-19: application-insights#REQ-OBAI-004 — KPI aggregations source (retroactive annotation) +- [x] task-20: application-insights#REQ-OBAI-005 — Activity payload sourced from getActionChartData (retroactive annotation) +- [x] task-21: application-insights#REQ-OBAI-006 — Cache-Control: public, max-age=60 on successful responses (retroactive annotation) +- [x] task-22: application-versions#REQ-OBV-103 — Manifest content change auto-bumps the patch component (retroactive annotation) +- [x] task-23: application-versions#REQ-OBV-104 — Cycle prevention on the promotesTo chain (retroactive annotation) +- [x] task-24: application-versions#REQ-OBV-107 — ApplicationVersion CRUD endpoints (retroactive annotation) +- [x] task-25: application-versions#REQ-OBV-108 — Version-deletion endpoint accepts a strategy (retroactive annotation) +- [x] task-26: green-field-migration#REQ-OBGFM-001 — Destructive migration repair step (retroactive annotation) +- [x] task-27: green-field-migration#REQ-OBGFM-002 — Migration is idempotent via versioned-shape short-circuit (retroactive annotation) +- [x] task-28: green-field-migration#REQ-OBGFM-003 — One log line per deleted Application (retroactive annotation) +- [x] task-29: green-field-migration#REQ-OBGFM-004 — Per-app register deletion uses OR's register-delete API (retroactive annotation) +- [x] task-30: openbuilt-application-register#REQ-OBA-007 — Migration populates permissions for pre-existing Applications (retroactive annotation) +- [x] task-31: openbuilt-application-register#REQ-OBA-008 — Application carries a productionVersion relation (retroactive annotation) +- [x] task-32: openbuilt-application-register#REQ-OBV-105 — REQ-OBV-105 (retroactive annotation) +- [x] task-33: openbuilt-exporter#Export is asynchronous via Nextcloud's IJob — Export is asynchronous via Nextcloud's IJob (retroactive annotation) +- [x] task-34: openbuilt-exporter#Export target — GitHub repository — Export target — GitHub repository (retroactive annotation) +- [x] task-35: openbuilt-exporter#Export target — ZIP archive — Export target — ZIP archive (retroactive annotation) +- [x] task-36: openbuilt-exporter#Export target — ZIP archive (24h expiry + purge) — Export target — ZIP archive (retroactive annotation) +- [x] task-37: openbuilt-exporter#ExportJob schema declaration — ExportJob schema declaration (retroactive annotation) +- [x] task-38: openbuilt-exporter#ExportJob schema declaration (lifecycle) — ExportJob schema declaration (retroactive annotation) +- [x] task-39: openbuilt-exporter#ExportJob schema declaration (status access) — ExportJob schema declaration (retroactive annotation) +- [x] task-40: openbuilt-exporter#Exported tree shape conforms to the nextcloud-app-template baseline — Exported tree shape conforms to the nextcloud-app-template baseline (retroactive annotation) +- [x] task-41: openbuilt-exporter#Re-exports are idempotent — Re-exports are idempotent (retroactive annotation) +- [x] task-42: openbuilt-page-designer#REQ-OBPD-001 — Menu tree editor with two-level nesting (retroactive annotation) +- [x] task-43: openbuilt-page-designer#REQ-OBPD-004 — Index-page sub-editor: register, schema, columns, actions (retroactive annotation) +- [x] task-44: openbuilt-page-designer#REQ-OBPD-009 — Save flow PUTs the manifest via OpenRegister REST (retroactive annotation) +- [x] task-45: openbuilt-rbac#REQ-OBR-006(rbac-clone) — REQ-OBR-006(rbac-clone) (retroactive annotation) +- [x] task-46: openbuilt-rbac#REQ-OBR-007(rbac-clone) — REQ-OBR-007(rbac-clone) (retroactive annotation) +- [x] task-47: openbuilt-rbac#REQ-OBRBAC-002 — Manifest endpoint enforces role membership (retroactive annotation) +- [x] task-48: openbuilt-rbac#REQ-OBRBAC-003 — Application list filters out unauthorised entries (retroactive annotation) +- [x] task-49: openbuilt-rbac#REQ-OBRBAC-007 — Permission changes are recorded in the OR audit trail (retroactive annotation) +- [x] task-50: openbuilt-runtime#REQ-OBR-001 — Manifest endpoint per virtual-app slug (retroactive annotation) +- [x] task-51: openbuilt-runtime#REQ-OBR-006(rbac-clone) — Manifest endpoint returns 403 for unauthorised callers (retroactive annotation) +- [x] task-52: openbuilt-runtime#REQ-OBR-009 — Caller's group set is provided via initial state (retroactive annotation) +- [x] task-53: openbuilt-schema-designer#REQ-OBSD-006 — Live validation and explicit Save persist to OR's runtime schema CRUD (retroactive annotation) +- [x] task-54: openbuilt-template-catalogue#REQ-OBTC-002 — Four Conduction-curated templates seeded via repair step (retroactive annotation) +- [x] task-55: openbuilt-template-catalogue#REQ-OBTC-004 — "Use this template" clones into a new Application (retroactive annotation) +- [x] task-56: openbuilt-template-catalogue#REQ-OBTC-005 — Cloned companion schemas are namespaced by Application slug (retroactive annotation) +- [x] task-57: openbuilt-template-catalogue#REQ-OBTC-009 — Template manifests validate against the canonical app-manifest schema (retroactive annotation) +- [x] task-58: openbuilt-version-snapshots#REQ-OBV-005 — Diff endpoint returns two manifest blobs in one call (retroactive annotation) +- [x] task-59: version-promotion#REQ-OBVP-001 — Promotion endpoint accepts a strategy and targets `sourceVersion.promotesTo` (retroactive annotation) +- [x] task-60: version-promotion#REQ-OBVP-002 — `start-with-source-data` replaces target rows + imports source schema set (retroactive annotation) +- [x] task-61: version-promotion#REQ-OBVP-003 — `migrate-existing-data` keeps target rows + imports source schema set (retroactive annotation) +- [x] task-62: version-promotion#REQ-OBVP-004 — `empty-start` drops target rows + imports source schema set (retroactive annotation) +- [x] task-63: version-promotion#REQ-OBVP-005 — Schema diff handling deferred to OR (retroactive annotation) +- [x] task-64: version-promotion#REQ-OBVP-006 — OR object lock acquisition on target + 409 on contention (retroactive annotation) +- [x] task-65: version-promotion#REQ-OBVP-007 — Permission: editor or owner on parent Application required (retroactive annotation) +- [x] task-66: version-promotion#REQ-OBVP-008 — Semver: target inherits source's value uniformly (retroactive annotation) +- [x] task-67: version-promotion#REQ-OBVP-009 — On-failure target flips to archived and endpoint returns 500 (retroactive annotation) +- [x] task-68: version-promotion#REQ-OBVP-011 — Default-strategy rule is a pure function of chain position (retroactive annotation) +- [x] task-69: version-routing#REQ-OBVR-001 — Manifest endpoint accepts optional `?_version=` query param (retroactive annotation) +- [x] task-70: version-routing#REQ-OBVR-002 — `ManifestResolverService` owns the two-step slug resolution (retroactive annotation) +- [x] task-71: version-routing#REQ-OBVR-003 — RBAC gate: editor/owner required for non-production versions; 404 (not 403) on failure (retroactive annotation) diff --git a/openspec/coverage-report.json b/openspec/coverage-report.json new file mode 100644 index 00000000..b902b16f --- /dev/null +++ b/openspec/coverage-report.json @@ -0,0 +1,339 @@ +{ + "generated_at": "2026-05-24T00:00:00Z", + "app": "openbuilt", + "branch": "development", + "scanner_version": "1", + "buckets": { + "annotated": [], + "plumbing": [ + {"file": "lib/AppInfo/Application.php", "method": "__construct", "reason": "empty-constructor"}, + {"file": "lib/AppInfo/Application.php", "method": "register", "reason": "DI registration only"}, + {"file": "lib/AppInfo/Application.php", "method": "boot", "reason": "service-bootstrap, dispatches to AppNavigationService::registerNavEntries"}, + {"file": "lib/Controller/HealthController.php", "method": "__construct", "reason": "empty-constructor"}, + {"file": "lib/Controller/HealthController.php", "method": "index", "reason": "liveness probe — returns {status:ok}"}, + {"file": "lib/Controller/MetricsController.php", "method": "__construct", "reason": "empty-constructor"}, + {"file": "lib/Controller/MetricsController.php", "method": "index", "reason": "Prometheus stub — empty metric set per file docblock"}, + {"file": "lib/Controller/SettingsController.php", "method": "__construct", "reason": "empty-constructor"}, + {"file": "lib/Controller/SettingsController.php", "method": "index", "reason": "delegates to SettingsService::getSettings"}, + {"file": "lib/Controller/SettingsController.php", "method": "create", "reason": "delegates to SettingsService::updateSettings"}, + {"file": "lib/Controller/SettingsController.php", "method": "load", "reason": "delegates to SettingsService::reloadConfiguration"}, + {"file": "lib/Sections/SettingsSection.php", "method": "__construct", "reason": "DI-only"}, + {"file": "lib/Sections/SettingsSection.php", "method": "getID", "reason": "static metadata"}, + {"file": "lib/Sections/SettingsSection.php", "method": "getName", "reason": "static metadata"}, + {"file": "lib/Sections/SettingsSection.php", "method": "getPriority", "reason": "static metadata"}, + {"file": "lib/Sections/SettingsSection.php", "method": "getIcon", "reason": "static metadata"}, + {"file": "lib/Settings/AdminSettings.php", "method": "__construct", "reason": "DI-only"}, + {"file": "lib/Settings/AdminSettings.php", "method": "getForm", "reason": "renders TemplateResponse, no logic"}, + {"file": "lib/Settings/AdminSettings.php", "method": "getSection", "reason": "static metadata"}, + {"file": "lib/Settings/AdminSettings.php", "method": "getPriority", "reason": "static metadata"}, + {"file": "lib/Service/SettingsService.php", "method": "__construct", "reason": "DI-only"}, + {"file": "lib/Service/SettingsService.php", "method": "isOpenRegisterAvailable", "reason": "delegates to IAppManager"}, + {"file": "lib/Service/SettingsService.php", "method": "getSettings", "reason": "IAppConfig read"}, + {"file": "lib/Service/SettingsService.php", "method": "updateSettings", "reason": "IAppConfig write"}, + {"file": "lib/Service/SettingsService.php", "method": "loadConfiguration", "reason": "wrapper for doLoadConfiguration"}, + {"file": "lib/Service/SettingsService.php", "method": "reloadConfiguration", "reason": "wrapper for doLoadConfiguration(force=true)"}, + {"file": "lib/Repair/InitializeSettings.php", "method": "__construct", "reason": "DI-only"}, + {"file": "lib/Repair/InitializeSettings.php", "method": "getName", "reason": "static metadata"}, + {"file": "lib/Repair/InitializeSettings.php", "method": "run", "reason": "delegates to SettingsService::loadConfiguration"} + ], + "bucket_1": [ + {"file": "lib/Controller/ApplicationsController.php", "method": "getManifest", "capability": "openbuilt-runtime", "req_id": "REQ-OBR-001 / REQ-OBR-006(rbac-clone)", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ explicitly; routes.php names this method for /api/applications/{slug}/manifest"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "resolveVersionedManifestResponse", "capability": "version-routing", "req_id": "REQ-OBVR-001 / REQ-OBVR-002", "confidence": 0.90, "needs_review": false, "signal": "private helper called from getManifest when ?_version is present; delegates to ManifestResolverService::resolve"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "diffVersions", "capability": "openbuilt-version-snapshots", "req_id": "REQ-OBV-005", "confidence": 0.95, "needs_review": false, "signal": "routes.php comment cites REQ-OBV-005 explicitly; file docblock cites openbuilt-versioning REQ-OBV-005"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "resolveVersionBlob", "capability": "openbuilt-version-snapshots", "req_id": "REQ-OBV-005", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — sole caller is diffVersions"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "resolveApplicationBySlug", "capability": "openbuilt-runtime", "req_id": "REQ-OBR-001", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — multi-caller helper used by getManifest, diffVersions, listMine, createFromTemplate"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "listMine", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002 / REQ-OBRBAC-003 / REQ-OBR-007(rbac-clone)", "confidence": 0.95, "needs_review": false, "signal": "file docblock + routes.php both cite REQ-OBRBAC-002 + REQ-OBR-007 explicitly"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "filterApplicationsByRole", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-003", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — helper of listMine; name matches REQ scenario"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "requirePermission", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002 / REQ-OBR-006(rbac-clone)", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — used by getManifest 403 branch"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "recordAdminBypass", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-007", "confidence": 0.90, "needs_review": false, "signal": "EVENT_ADMIN_BYPASS constant cites REQ-OBRBAC-006; method emits audit-trail entry per REQ-OBRBAC-007"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "getUserGroupIds", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — group-set helper used by RBAC checks"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "collectAuthorisedGroups", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — helper of requirePermission"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "classifyPrincipal", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — principal-shape helper"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "createFromTemplate", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-004 / REQ-OBTC-005", "confidence": 0.95, "needs_review": false, "signal": "file + routes.php both cite REQ-OBTC-004 + REQ-OBTC-005 explicitly"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "resolveSharedContext", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-004", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — helper of createFromTemplate"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "buildClonedManifest", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — invokes rewriteSchemaRefs for namespacing"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "provisionPerAppArtifacts", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — provisions per-app register + clones companion schemas"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "persistApplication", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-004", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — clone persistence step"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "validateCloneRequest", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-004", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — clone-request validation helper"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "extractCompanionSchemas", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — companion-schema walker"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "buildRewriteMap", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — namespace-rewriting map"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "provisionPerAppRegister", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit — provisions openbuilt-{slug} register per REQ-OBTC-005 scenario"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "findOrCreateRegister", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — register-mapper helper"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "extractRegisterOwner", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit — register-owner accessor"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "cloneCompanionSchemas", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit — deep-copies companion schemas into the per-app namespace"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "rewriteSchemaRefs", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — rewrites manifest schema refs"}, + {"file": "lib/Controller/ApplicationsController.php", "method": "lookupOne", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-005", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit — generic single-result lookup"}, + + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "index", "capability": "application-versions", "req_id": "REQ-OBV-107", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBV-107 explicitly; routes.php cross-refs"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "show", "capability": "application-versions", "req_id": "REQ-OBV-107", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBV-107 explicitly"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "create", "capability": "application-versions", "req_id": "REQ-OBV-107", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBV-107 explicitly"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "update", "capability": "application-versions", "req_id": "REQ-OBV-107", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBV-107 explicitly"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "destroy", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBV-108 explicitly (strategy-aware delete)"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "loadApplication", "capability": "application-versions", "req_id": "REQ-OBV-107", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "findVersionForApplication", "capability": "application-versions", "req_id": "REQ-OBV-107", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "requireRole", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — role-gate on CRUD endpoints per file docblock"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "collectAuthorisedPrincipals", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "absorbPrincipalBucket", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Controller/ApplicationVersionsController.php", "method": "getUserGroupIds", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + + {"file": "lib/Controller/ApplicationCreationController.php", "method": "wizard", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-001 / REQ-OBWIZ-007 / REQ-OBWIZ-010", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBWIZ-001+007+010; routes.php names endpoint with REQ-OBWIZ-001"}, + {"file": "lib/Controller/ApplicationCreationController.php", "method": "collectPayload", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-001", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — request-body decode helper"}, + + {"file": "lib/Controller/ApplicationInsightsController.php", "method": "getInsights", "capability": "application-insights", "req_id": "REQ-OBAI-001 / REQ-OBAI-006 / REQ-OBAI-002", "confidence": 0.95, "needs_review": false, "signal": "file docblock + method docblock both cite REQ-OBAI-001 + REQ-OBAI-006 + REQ-OBAI-002"}, + + {"file": "lib/Controller/VersionPromotionController.php", "method": "promote", "capability": "version-promotion", "req_id": "REQ-OBVP-001 / REQ-OBVP-007", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBVP-001 + REQ-OBVP-007 explicitly"}, + {"file": "lib/Controller/VersionPromotionController.php", "method": "mapExceptionToResponse", "capability": "version-promotion", "req_id": "REQ-OBVP-006 / REQ-OBVP-009", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — maps Locked + PromotionFailed exceptions to 409/500"}, + {"file": "lib/Controller/VersionPromotionController.php", "method": "buildLockedResponse", "capability": "version-promotion", "req_id": "REQ-OBVP-006", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — 409 on object-lock contention"}, + {"file": "lib/Controller/VersionPromotionController.php", "method": "loadApplication", "capability": "version-promotion", "req_id": "REQ-OBVP-001", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Controller/VersionPromotionController.php", "method": "loadVersion", "capability": "version-promotion", "req_id": "REQ-OBVP-001", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Controller/VersionPromotionController.php", "method": "assertEditorOrOwner", "capability": "version-promotion", "req_id": "REQ-OBVP-007", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit — name maps directly to REQ-OBVP-007 spec wording"}, + + {"file": "lib/Controller/IconController.php", "method": "iconLight", "capability": "app-icon-management", "req_id": "REQ-OBICON-002", "confidence": 0.95, "needs_review": false, "signal": "method + file docblock + routes.php all cite REQ-OBICON-002"}, + {"file": "lib/Controller/IconController.php", "method": "iconDark", "capability": "app-icon-management", "req_id": "REQ-OBICON-003", "confidence": 0.95, "needs_review": false, "signal": "method + file docblock + routes.php all cite REQ-OBICON-003"}, + {"file": "lib/Controller/IconController.php", "method": "buildIconResponse", "capability": "app-icon-management", "req_id": "REQ-OBICON-002 / REQ-OBICON-003", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — shared body of both icon endpoints"}, + + {"file": "lib/Controller/ExportsController.php", "method": "submit", "capability": "openbuilt-exporter", "req_id": "ExportJob/Async/IJob requirement (Export is asynchronous)", "confidence": 0.90, "needs_review": false, "signal": "endpoint POST /api/applications/{slug}/exports — exporter spec mandates 202 Accepted async pattern"}, + {"file": "lib/Controller/ExportsController.php", "method": "download", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive", "confidence": 0.90, "needs_review": false, "signal": "GET /api/exports/{uuid}/download — exporter spec mandates this URL shape"}, + {"file": "lib/Controller/ExportsController.php", "method": "isAuthorisedForApplication", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.80, "needs_review": false, "signal": "file docblock cites ADR-005 IDOR guard; openbuilt-rbac REQ-OBRBAC-002 is the canonical authorisation REQ"}, + {"file": "lib/Controller/ExportsController.php", "method": "fallbackAuthoriseViaOrLookup", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-002", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Controller/ExportsController.php", "method": "isAuthorisedForJob", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration (status access)", "confidence": 0.70, "needs_review": true, "signal": "Per-job authorisation; mid-confidence — could also belong to openbuilt-rbac"}, + {"file": "lib/Controller/ExportsController.php", "method": "validateSubmitBody", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — request-body validation per ExportJob schema"}, + {"file": "lib/Controller/ExportsController.php", "method": "validateGithubFields", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — GitHub-specific field validation"}, + + {"file": "lib/Controller/DashboardController.php", "method": "page", "capability": "openbuilt-runtime", "req_id": "REQ-OBR-009", "confidence": 0.90, "needs_review": false, "signal": "file docblock cites REQ-OBR-009 — IInitialState publication of currentUserGroups"}, + {"file": "lib/Controller/DashboardController.php", "method": "catchAll", "capability": "openbuilt-runtime", "req_id": "REQ-OBR-009", "confidence": 0.80, "needs_review": false, "signal": "SPA fall-through twin of page() — same initial-state publication"}, + {"file": "lib/Controller/DashboardController.php", "method": "publishCurrentUserGroups", "capability": "openbuilt-runtime", "req_id": "REQ-OBR-009", "confidence": 0.95, "needs_review": false, "signal": "method name = REQ scenario verb"}, + + {"file": "lib/Service/AppNavigationService.php", "method": "registerNavEntries", "capability": "app-nav-entries", "req_id": "REQ-OBNAV-001", "confidence": 0.95, "needs_review": false, "signal": "name matches REQ scenario verb verbatim"}, + {"file": "lib/Service/AppNavigationService.php", "method": "isVisibleForCurrentUser", "capability": "app-nav-entries", "req_id": "REQ-OBNAV-002 / REQ-OBNAV-003", "confidence": 0.95, "needs_review": false, "signal": "file docblock lists the four permission rules from REQ-OBNAV-002/003"}, + {"file": "lib/Service/AppNavigationService.php", "method": "flattenPermissions", "capability": "app-nav-entries", "req_id": "REQ-OBNAV-002", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — used by isVisibleForCurrentUser"}, + {"file": "lib/Service/AppNavigationService.php", "method": "principalsMatchGroups", "capability": "app-nav-entries", "req_id": "REQ-OBNAV-002", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/AppNavigationService.php", "method": "getPublishedApplications", "capability": "app-nav-entries", "req_id": "REQ-OBNAV-001 / REQ-OBNAV-004", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — per-request fetch matches REQ-OBNAV-004 'no writeback'"}, + + {"file": "lib/Service/ApplicationCreationService.php", "method": "createApplication", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-007 / REQ-OBWIZ-008 / REQ-OBWIZ-009 / REQ-OBWIZ-010", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBWIZ-007..010; flow comment maps each step to spec"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "validatePayload", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-005 / REQ-OBWIZ-006", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit — validates slugs + chain uniqueness"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "resolveVersionChain", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-002 / REQ-OBWIZ-006", "confidence": 0.85, "needs_review": false, "signal": "REQ scenario noun match"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "appSlugExists", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-005", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "provisionRegister", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-008", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit — per-version register provisioning matches REQ-OBWIZ-008"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "deleteRegister", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-007", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — rollback helper"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "rollback", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-007", "confidence": 0.95, "needs_review": false, "signal": "name = REQ-OBWIZ-007 spec wording"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "loadDefaultManifest", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-009", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — initial manifest per REQ-OBWIZ-009"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "loadDefaultSchemas", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-008", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — seed schema set"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "substituteRegisterSlug", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-008", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationCreationService.php", "method": "substituteVersionContext", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-009", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + + {"file": "lib/Service/ApplicationInsightsService.php", "method": "requireAuthorisedCaller", "capability": "application-insights", "req_id": "REQ-OBAI-002", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBAI-002; name matches REQ scenario"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "computeInsights", "capability": "application-insights", "req_id": "REQ-OBAI-001 / REQ-OBAI-004 / REQ-OBAI-005", "confidence": 0.95, "needs_review": false, "signal": "name + file docblock; fans out to KPI + chart sources per REQ-OBAI-004/005"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "deriveSchemaIds", "capability": "application-insights", "req_id": "REQ-OBAI-003", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBAI-003 schema-set walk explicitly"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "extractSchemaIdForRegister", "capability": "application-insights", "req_id": "REQ-OBAI-003", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — helper of deriveSchemaIds"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "isAuthorised", "capability": "application-insights", "req_id": "REQ-OBAI-002", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "callerInAnyRole", "capability": "application-insights", "req_id": "REQ-OBAI-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "safeDistinctActorCount", "capability": "application-insights", "req_id": "REQ-OBAI-004", "confidence": 0.85, "needs_review": false, "signal": "Active-users KPI per REQ-OBAI-004"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "countObjects", "capability": "application-insights", "req_id": "REQ-OBAI-004", "confidence": 0.85, "needs_review": false, "signal": "Object-count KPI per REQ-OBAI-004"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "countAttachedFiles", "capability": "application-insights", "req_id": "REQ-OBAI-004", "confidence": 0.85, "needs_review": false, "signal": "Files KPI per REQ-OBAI-004"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "countAuditEvents", "capability": "application-insights", "req_id": "REQ-OBAI-004", "confidence": 0.85, "needs_review": false, "signal": "Audit-events KPI per REQ-OBAI-004"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "buildActivityTimeline", "capability": "application-insights", "req_id": "REQ-OBAI-005", "confidence": 0.90, "needs_review": false, "signal": "Activity payload per REQ-OBAI-005"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "sumChartSeries", "capability": "application-insights", "req_id": "REQ-OBAI-005", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "mergeChartIntoBuckets", "capability": "application-insights", "req_id": "REQ-OBAI-005", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationInsightsService.php", "method": "mergeSeriesData", "capability": "application-insights", "req_id": "REQ-OBAI-005", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + + {"file": "lib/Service/ApplicationVersionService.php", "method": "canonicaliseManifest", "capability": "application-versions", "req_id": "REQ-OBV-103", "confidence": 0.90, "needs_review": false, "signal": "Manifest canonicalisation for SHA-256 diff per REQ-OBV-103 auto-bump"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "hashManifest", "capability": "application-versions", "req_id": "REQ-OBV-103", "confidence": 0.90, "needs_review": false, "signal": "Same"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "bumpPatch", "capability": "application-versions", "req_id": "REQ-OBV-103", "confidence": 0.95, "needs_review": false, "signal": "name = REQ-OBV-103 wording"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "onSave", "capability": "application-versions", "req_id": "REQ-OBV-103", "confidence": 0.85, "needs_review": false, "signal": "Save hook that triggers auto-bump"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "guardNoCycle", "capability": "application-versions", "req_id": "REQ-OBV-104", "confidence": 0.95, "needs_review": false, "signal": "name = REQ-OBV-104 wording (cycle prevention)"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "guardProductionVersionOwnership", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-008", "confidence": 0.90, "needs_review": false, "signal": "file + listener docblock cite REQ-OBA-008 + REQ-OBV-105"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "deleteVersion", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.95, "needs_review": false, "signal": "Strategy-aware delete per REQ-OBV-108"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "assertValidStrategy", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "assertNotProductionVersion", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — guards strategy delete"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "dropPerVersionRegister", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — delete-now strategy"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "flagRegisterOrphaned", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — orphan-grace strategy"}, + {"file": "lib/Service/ApplicationVersionService.php", "method": "resolveNextPromotesTo", "capability": "application-versions", "req_id": "REQ-OBV-108", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + + {"file": "lib/Service/ManifestResolverService.php", "method": "resolve", "capability": "version-routing", "req_id": "REQ-OBVR-002 / REQ-OBVR-003", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBVR-002 + REQ-OBVR-003 explicitly"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "checkNonProductionAccess", "capability": "version-routing", "req_id": "REQ-OBVR-003", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "findApplicationBySlug", "capability": "version-routing", "req_id": "REQ-OBVR-002", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — step 1 of two-step resolution"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "resolveProductionManifest", "capability": "version-routing", "req_id": "REQ-OBVR-002", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "findVersionBySlug", "capability": "version-routing", "req_id": "REQ-OBVR-002", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — step 2 of two-step resolution"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "findVersionBySlugFallback", "capability": "version-routing", "req_id": "REQ-OBVR-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "findVersionByUuid", "capability": "version-routing", "req_id": "REQ-OBVR-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "isCallerAuthorised", "capability": "version-routing", "req_id": "REQ-OBVR-003", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ManifestResolverService.php", "method": "bucketContainsUid", "capability": "version-routing", "req_id": "REQ-OBVR-003", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + + {"file": "lib/Service/IconService.php", "method": "getIconStream", "capability": "app-icon-management", "req_id": "REQ-OBICON-002 / REQ-OBICON-003", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites Decision 2 fallback chain for REQ-OBICON-002/003"}, + {"file": "lib/Service/IconService.php", "method": "fetchApplication", "capability": "app-icon-management", "req_id": "REQ-OBICON-001", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/IconService.php", "method": "resolveIconLight", "capability": "app-icon-management", "req_id": "REQ-OBICON-002", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — light fallback chain"}, + {"file": "lib/Service/IconService.php", "method": "resolveIconDark", "capability": "app-icon-management", "req_id": "REQ-OBICON-003", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — dark fallback chain"}, + {"file": "lib/Service/IconService.php", "method": "streamForIconField", "capability": "app-icon-management", "req_id": "REQ-OBICON-001 / REQ-OBICON-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/IconService.php", "method": "fetchAttachedFileStream", "capability": "app-icon-management", "req_id": "REQ-OBICON-001", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/IconService.php", "method": "fallbackStream", "capability": "app-icon-management", "req_id": "REQ-OBICON-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — /img/app.svg fallback"}, + + {"file": "lib/Service/VersionPromotionService.php", "method": "defaultStrategyFor", "capability": "version-promotion", "req_id": "REQ-OBVP-011", "confidence": 0.95, "needs_review": false, "signal": "static method name maps to REQ-OBVP-011 (default-strategy rule)"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "promote", "capability": "version-promotion", "req_id": "REQ-OBVP-001", "confidence": 0.95, "needs_review": false, "signal": "Top-level entry point cited by file docblock"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "runStrategy", "capability": "version-promotion", "req_id": "REQ-OBVP-002 / REQ-OBVP-003 / REQ-OBVP-004", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit — strategy dispatcher"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "runStartWithSourceData", "capability": "version-promotion", "req_id": "REQ-OBVP-002", "confidence": 0.95, "needs_review": false, "signal": "name = REQ-OBVP-002 strategy wording"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "runMigrateExistingData", "capability": "version-promotion", "req_id": "REQ-OBVP-003", "confidence": 0.95, "needs_review": false, "signal": "name = REQ-OBVP-003 strategy wording"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "runEmptyStart", "capability": "version-promotion", "req_id": "REQ-OBVP-004", "confidence": 0.95, "needs_review": false, "signal": "name = REQ-OBVP-004 strategy wording"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "forwardSchemaSetToOR", "capability": "version-promotion", "req_id": "REQ-OBVP-005", "confidence": 0.90, "needs_review": false, "signal": "Schema-diff deferred to OR per REQ-OBVP-005"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "wipeTargetRegister", "capability": "version-promotion", "req_id": "REQ-OBVP-002 / REQ-OBVP-004", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "copyRowsFromSource", "capability": "version-promotion", "req_id": "REQ-OBVP-002", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "applyManifestAndSemver", "capability": "version-promotion", "req_id": "REQ-OBVP-008", "confidence": 0.95, "needs_review": false, "signal": "Target inherits source semver per REQ-OBVP-008"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "handlePromotionFailure", "capability": "version-promotion", "req_id": "REQ-OBVP-009", "confidence": 0.95, "needs_review": false, "signal": "On-failure target flips to archived per REQ-OBVP-009"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "acquireLock", "capability": "version-promotion", "req_id": "REQ-OBVP-006", "confidence": 0.95, "needs_review": false, "signal": "OR object lock per REQ-OBVP-006"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "releaseLock", "capability": "version-promotion", "req_id": "REQ-OBVP-006", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/VersionPromotionService.php", "method": "callGetLockInfo", "capability": "version-promotion", "req_id": "REQ-OBVP-006", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + + {"file": "lib/Service/SlugValidator.php", "method": "validateAppSlug", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-005", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBWIZ-005 explicitly"}, + {"file": "lib/Service/SlugValidator.php", "method": "validateVersionSlug", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-005", "confidence": 0.95, "needs_review": false, "signal": "Same — version slug variant"}, + {"file": "lib/Service/SlugValidator.php", "method": "validateChainSlugs", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-006", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBWIZ-006 explicitly"}, + + {"file": "lib/Service/ExportService.php", "method": "generateAppZip", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.90, "needs_review": false, "signal": "ZIP archive generation"}, + {"file": "lib/Service/ExportService.php", "method": "packageZip", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive", "confidence": 0.90, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ExportService.php", "method": "listFilesSorted", "capability": "openbuilt-exporter", "req_id": "Re-exports are idempotent", "confidence": 0.80, "needs_review": false, "signal": "Sorted file walk — supports byte-equivalent ZIP requirement"}, + {"file": "lib/Service/ExportService.php", "method": "resolvePlaceholders", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.90, "needs_review": false, "signal": "Placeholder substitution per spec scenario 'no unresolved {{placeholder}} tokens'"}, + {"file": "lib/Service/ExportService.php", "method": "isBinary", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit — binary skip in placeholder pass"}, + {"file": "lib/Service/ExportService.php", "method": "copyTemplate", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — copies lib/Resources/template/"}, + {"file": "lib/Service/ExportService.php", "method": "prepareScratchDir", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/ExportService.php", "method": "getOrCreateAppDataDir", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — appdata storage per spec"}, + {"file": "lib/Service/ExportService.php", "method": "rrmdir", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit — scratch cleanup helper"}, + + {"file": "lib/Service/ExportJobService.php", "method": "queue", "capability": "openbuilt-exporter", "req_id": "Export is asynchronous via Nextcloud's IJob", "confidence": 0.95, "needs_review": false, "signal": "Queues ExportJob via IJobList"}, + {"file": "lib/Service/ExportJobService.php", "method": "persistJob", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration", "confidence": 0.85, "needs_review": false, "signal": "Persists ExportJob OR record"}, + {"file": "lib/Service/ExportJobService.php", "method": "transitionJob", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration (lifecycle)", "confidence": 0.90, "needs_review": false, "signal": "Lifecycle transition queued→running→succeeded|failed"}, + {"file": "lib/Service/ExportJobService.php", "method": "mergeJobFields", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit — field merge helper"}, + {"file": "lib/Service/ExportJobService.php", "method": "resolveDownload", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive", "confidence": 0.90, "needs_review": false, "signal": "Resolves download URL + expiry"}, + {"file": "lib/Service/ExportJobService.php", "method": "fetchPat", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.95, "needs_review": false, "signal": "ICredentialsManager-backed PAT per spec REQ"}, + {"file": "lib/Service/ExportJobService.php", "method": "clearPat", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.95, "needs_review": false, "signal": "PAT wipe on terminal state per spec scenario"}, + {"file": "lib/Service/ExportJobService.php", "method": "credentialKey", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Service/ExportJobService.php", "method": "uuid4", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration", "confidence": 0.65, "needs_review": true, "signal": "UUID helper used by queue()"}, + + {"file": "lib/Service/GitHubPushService.php", "method": "push", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.95, "needs_review": false, "signal": "file docblock describes the spec REQ; note Phase-1 stub flagged in Bucket 4"}, + {"file": "lib/Service/GitHubPushService.php", "method": "resolveDefaultBranch", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — PR default-branch resolution"}, + + {"file": "lib/Service/PlaceholderResolver.php", "method": "buildMap", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.85, "needs_review": false, "signal": "Builds {{placeholder}} substitution map"}, + {"file": "lib/Service/PlaceholderResolver.php", "method": "resolve", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.85, "needs_review": false, "signal": "Resolves single string"}, + {"file": "lib/Service/PlaceholderResolver.php", "method": "slug", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Service/PlaceholderResolver.php", "method": "pascalCase", "capability": "openbuilt-exporter", "req_id": "Exported tree shape conforms to the nextcloud-app-template baseline", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit — namespace casing"}, + + {"file": "lib/Listener/ProductionVersionGuardListener.php", "method": "handle", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-008 / REQ-OBV-105", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites both REQs explicitly"}, + {"file": "lib/Listener/ProductionVersionGuardListener.php", "method": "extractSchemaSlug", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-008", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Listener/ProductionVersionGuardListener.php", "method": "extractObjectData", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-008", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/Listener/ProductionVersionGuardListener.php", "method": "extractUuid", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-008", "confidence": 0.65, "needs_review": true, "signal": "Pass B inherit"}, + + {"file": "lib/BackgroundJob/RunExportJob.php", "method": "run", "capability": "openbuilt-exporter", "req_id": "Export is asynchronous via Nextcloud's IJob", "confidence": 0.95, "needs_review": false, "signal": "Implements IJob per spec REQ"}, + {"file": "lib/BackgroundJob/RunExportJob.php", "method": "extractJobUuid", "capability": "openbuilt-exporter", "req_id": "Export is asynchronous via Nextcloud's IJob", "confidence": 0.70, "needs_review": true, "signal": "Pass B inherit"}, + {"file": "lib/BackgroundJob/RunExportJob.php", "method": "executePipeline", "capability": "openbuilt-exporter", "req_id": "Export is asynchronous via Nextcloud's IJob", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — drives the lifecycle pipeline"}, + {"file": "lib/BackgroundJob/RunExportJob.php", "method": "maybePush", "capability": "openbuilt-exporter", "req_id": "Export target — GitHub repository", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — invokes GitHubPushService"}, + {"file": "lib/BackgroundJob/RunExportJob.php", "method": "buildSuccessFields", "capability": "openbuilt-exporter", "req_id": "ExportJob schema declaration", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit"}, + + {"file": "lib/BackgroundJob/CleanupExpiredExports.php", "method": "run", "capability": "openbuilt-exporter", "req_id": "Export target — ZIP archive (24h expiry + purge)", "confidence": 0.95, "needs_review": false, "signal": "file docblock matches REQ scenario 'purges expired archives'"}, + + {"file": "lib/Repair/MigrateToVersionedModel.php", "method": "run", "capability": "green-field-migration", "req_id": "REQ-OBGFM-001", "confidence": 0.95, "needs_review": false, "signal": "@destructive + file docblock matches REQ-OBGFM-001 spec"}, + {"file": "lib/Repair/MigrateToVersionedModel.php", "method": "isAlreadyVersioned", "capability": "green-field-migration", "req_id": "REQ-OBGFM-002", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBGFM-002 short-circuit guard"}, + {"file": "lib/Repair/MigrateToVersionedModel.php", "method": "enumerateApplications", "capability": "green-field-migration", "req_id": "REQ-OBGFM-001", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + {"file": "lib/Repair/MigrateToVersionedModel.php", "method": "migrateOne", "capability": "green-field-migration", "req_id": "REQ-OBGFM-003 / REQ-OBGFM-004", "confidence": 0.85, "needs_review": false, "signal": "Pass B inherit — emits per-app log line + deletes per-app register"}, + + {"file": "lib/Repair/PopulateApplicationPermissions.php", "method": "run", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-007", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBA-007 (migration populates permissions)"}, + {"file": "lib/Repair/PopulateApplicationPermissions.php", "method": "needsMigration", "capability": "openbuilt-application-register", "req_id": "REQ-OBA-007", "confidence": 0.80, "needs_review": false, "signal": "Pass B inherit"}, + + {"file": "lib/Repair/SeedApplicationTemplates.php", "method": "run", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-002", "confidence": 0.95, "needs_review": false, "signal": "file docblock cites REQ-OBTC-002 + REQ-OBTC-009"}, + {"file": "lib/Repair/SeedApplicationTemplates.php", "method": "validateFixture", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-009", "confidence": 0.90, "needs_review": false, "signal": "file docblock cites REQ-OBTC-009 explicitly"}, + {"file": "lib/Repair/SeedApplicationTemplates.php", "method": "findBySlug", "capability": "openbuilt-template-catalogue", "req_id": "REQ-OBTC-002", "confidence": 0.75, "needs_review": true, "signal": "Pass B inherit — idempotency guard"}, + + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleCreateApp", "capability": "application-creation-wizard", "req_id": "REQ-OBWIZ-001", "confidence": 0.80, "needs_review": false, "signal": "MCP tool surface for creation flow"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handlePromoteVersion", "capability": "version-promotion", "req_id": "REQ-OBVP-001", "confidence": 0.80, "needs_review": false, "signal": "MCP tool surface for promotion flow"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleListApps", "capability": "openbuilt-rbac", "req_id": "REQ-OBRBAC-003", "confidence": 0.70, "needs_review": true, "signal": "MCP equivalent of listMine — same RBAC scenario"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleGetAppManifest", "capability": "openbuilt-runtime", "req_id": "REQ-OBR-001", "confidence": 0.80, "needs_review": false, "signal": "MCP tool surface for manifest read"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleUpsertSchema", "capability": "openbuilt-schema-designer", "req_id": "REQ-OBSD-006", "confidence": 0.75, "needs_review": true, "signal": "MCP tool surface for schema-designer save"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleUpsertPage", "capability": "openbuilt-page-designer", "req_id": "REQ-OBPD-009", "confidence": 0.75, "needs_review": true, "signal": "MCP tool surface for page-designer save"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleAddWidget", "capability": "openbuilt-page-designer", "req_id": "REQ-OBPD-004", "confidence": 0.70, "needs_review": true, "signal": "Widget add path; mid-confidence — could also belong to schema-designer"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "handleUpsertMenuItem", "capability": "openbuilt-page-designer", "req_id": "REQ-OBPD-001", "confidence": 0.80, "needs_review": false, "signal": "Menu-tree editor REQ"} + ], + "bucket_2a": { + "openbuilt-runtime": [ + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "getAppId", "observed_behavior": "Static MCP tool-provider metadata — no REQ coverage in openbuilt-runtime for MCP surface"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "getTools", "observed_behavior": "Returns the MCP tool catalogue. ADR-019 + integration-registry — outside any current openbuilt REQ"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "invokeTool", "observed_behavior": "Dispatch surface for all MCP tool calls"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "loadVersion", "observed_behavior": "MCP-internal version loader"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "saveVersionManifest", "observed_behavior": "MCP-internal manifest save"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "validateListAppsArgs", "observed_behavior": "MCP arg-shape validation"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "resolveApplicationBySlug", "observed_behavior": "MCP-internal slug resolution helper"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "mapApplication", "observed_behavior": "Maps OR row to MCP response shape"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "sourceDescriptor", "observed_behavior": "MCP source-of-truth descriptor"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "errorResult", "observed_behavior": "MCP error-envelope helper"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "requireAuthenticatedUser", "observed_behavior": "Authentication guard on every MCP tool call"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "isAdmin", "observed_behavior": "Admin-check helper for MCP"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "isValidSlug", "observed_behavior": "Slug-pattern check (duplicates SlugValidator scope)"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "buildDeepLink", "observed_behavior": "Deep-link URL builder for MCP responses"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "toArray", "observed_behavior": "Entity→array coercion"}, + {"file": "lib/Mcp/OpenBuiltToolProvider.php", "method": "extractUuid", "observed_behavior": "UUID accessor"} + ], + "app-icon-management": [ + {"file": "lib/Service/IconService.php", "method": "extractUuid", "observed_behavior": "UUID accessor — no scenario coverage in app-icon-management REQs"} + ] + }, + "bucket_2b": { + "deep-link-registration": [ + {"file": "lib/Listener/DeepLinkRegistrationListener.php", "method": "handle", "observed_behavior": "Registers OpenBuilt deep-link URL patterns with OpenRegister's search provider. No openbuilt spec covers this; ADR-019 / integration-registry is the closest concept but no REQ in this app's spec set names it."} + ] + }, + "bucket_3a": [], + "bucket_3b": [ + {"req": "openbuilt-application-register#REQ-OBA-001 Application schema registered in OpenRegister", "note": "Schema lives in lib/Settings/openbuilt_register.json — declarative, not a PHP method. No imperative implementation expected; reverse-pass keyword grep finds historical references in removed lines but they are register/schema config moves."}, + {"req": "openbuilt-application-register#REQ-OBA-002 Manifest blob is structurally valid", "note": "Server-side x-openregister-validation in openbuilt_register.json + client-side validateManifest in @conduction/nextcloud-vue. No app-local PHP method."}, + {"req": "openbuilt-application-register#REQ-OBA-003 Declarative lifecycle drives state transitions", "note": "x-openregister-lifecycle block in openbuilt_register.json. Declarative; no PHP method expected."}, + {"req": "openbuilt-application-register#REQ-OBA-004 BuiltAppRoute index for slug lookup", "note": "Declarative schema in openbuilt_register.json. Used by ApplicationsController::getManifest via OR REST."}, + {"req": "openbuilt-application-register#REQ-OBA-005 Multi-tenant scoping via OR organisation", "note": "OR-side cross-cutting concern; no openbuilt-side PHP implementation needed."}, + {"req": "openbuilt-application-register#REQ-OBA-006 Application schema carries a permissions block", "note": "Schema declaration in openbuilt_register.json. Declarative."}, + {"req": "application-versions#REQ-OBV-101 ApplicationVersion schema declared in OpenRegister", "note": "Declarative schema in openbuilt_register.json."}, + {"req": "application-versions#REQ-OBV-102 Initial semver is 0.1.0 on creation", "note": "Schema default in openbuilt_register.json + ApplicationCreationService::loadDefaultManifest; declarative-default. May warrant a Bucket 1 annotation on ApplicationCreationService::createApplication."}, + {"req": "application-versions#REQ-OBV-105 Production version is set explicitly on Application", "note": "Implemented via ProductionVersionGuardListener::handle + ApplicationVersionService::guardProductionVersionOwnership — already in Bucket 1 under REQ-OBA-008."}, + {"req": "application-versions#REQ-OBV-106 Lifecycle on ApplicationVersion drives BuiltAppRoute upsert", "note": "Declarative x-openregister-lifecycle in openbuilt_register.json; OR-side hook."}, + {"req": "openbuilt-rbac#REQ-OBRBAC-001 Permissions field shape and default on creation", "note": "Declarative schema. PopulateApplicationPermissions repair step (Bucket 1) covers the migration path."}, + {"req": "openbuilt-rbac#REQ-OBRBAC-004 Role-to-action mapping in editor UIs", "note": "Frontend-only — see useRole composable + per-view v-if guards (not in PHP scope)."}, + {"req": "openbuilt-rbac#REQ-OBRBAC-005 Transfer-ownership flow", "note": "No PHP transfer-ownership endpoint found in lib/Controller/. Frontend-only path appears to PUT directly via OR REST; recommend reverse-spec to confirm or remove."}, + {"req": "openbuilt-rbac#REQ-OBRBAC-006 Global openbuilt.use navigation-entry permission", "note": "Frontend permission used by AppNavigationService::isVisibleForCurrentUser via group:* sentinel — already in Bucket 1 for REQ-OBNAV-003. The standalone REQ-OBRBAC-006 wording is not separately implemented."}, + {"req": "openbuilt-runtime#REQ-OBR-004 Seeded hello-world Application", "note": "Looking at lib/Repair/, no SeedHelloWorld step is present in the live repo (it appears in git history per removed-lines cache). Hello-world seeding has been superseded by SeedApplicationTemplates + green-field-migration."}, + {"req": "openbuilt-runtime#REQ-OBR-002 / REQ-OBR-003 / REQ-OBR-005..REQ-OBR-013 (frontend)", "note": "Frontend REQs — implementations live in src/views/BuilderHost.vue, src/views/PageDesignerHost.vue, src/components/ApplicationCard.vue, src/composables/useRole.js etc. Frontend Bucket 1 mapping is out of scope for this pass; flagged here so a human knows the PHP scan is not the whole story."}, + {"req": "openbuilt-page-designer#REQ-OBPD-001..REQ-OBPD-011 (all)", "note": "Frontend-only spec. Implementations live in src/components/page-editor/* and src/views/PageDesigner.vue."}, + {"req": "openbuilt-schema-designer#REQ-OBSD-001..REQ-OBSD-008 (all)", "note": "Frontend-only spec. Implementations live in src/components/schema-editor/* and src/views/SchemaDesigner.vue."}, + {"req": "openbuilt-version-snapshots#REQ-OBV-002 / REQ-OBV-003", "note": "REQ-OBV-002 is OR-lifecycle declarative; REQ-OBV-003 (rollback) is frontend modal + OR PUT (src/modals/RollbackConfirmModal.vue + src/views/VersionHistory.vue)."}, + {"req": "version-routing#REQ-OBVR-004..REQ-OBVR-009 (frontend)", "note": "Frontend REQs — useApplicationVersion composable, buildVersionedRoute helper, schemas.js store. Out of scope for PHP scan."}, + {"req": "application-detail-overview#REQ-OBADO-001..REQ-OBADO-012 (all)", "note": "Frontend-only spec — applicationDetail/widgets/* components and ApplicationDetailHeader.vue."}, + {"req": "openbuilt-template-catalogue#REQ-OBTC-001 / REQ-OBTC-003 / REQ-OBTC-006..REQ-OBTC-010 (most)", "note": "Mixed: REQ-OBTC-001 declarative schema; REQ-OBTC-003 (Gallery view) + REQ-OBTC-006 (redirect to page editor) + REQ-OBTC-007 (one-shot snapshot semantics) + REQ-OBTC-008 (read-only via UI) + REQ-OBTC-010 (i18n) are frontend or schema."} + ], + "bucket_4": { + "missing-spec-in-file-docblock": [ + {"file": "lib/Repair/InitializeSettings.php", "rule": "missing @spec openspec/changes/... in file docblock — placeholder text from app scaffolding"}, + {"file": "lib/Listener/DeepLinkRegistrationListener.php", "rule": "missing @spec openspec/changes/... in file docblock — placeholder text from app scaffolding"}, + {"file": "lib/Controller/SettingsController.php", "rule": "missing @spec openspec/changes/... in file docblock — plumbing"}, + {"file": "lib/Controller/HealthController.php", "rule": "missing @spec openspec/changes/... in file docblock — plumbing"}, + {"file": "lib/Controller/MetricsController.php", "rule": "missing @spec openspec/changes/... in file docblock — plumbing"}, + {"file": "lib/Controller/DashboardController.php", "rule": "missing @spec openspec/changes/... in file docblock"}, + {"file": "lib/Sections/SettingsSection.php", "rule": "missing @spec openspec/changes/... in file docblock — plumbing"}, + {"file": "lib/Settings/AdminSettings.php", "rule": "missing @spec openspec/changes/... in file docblock — plumbing"} + ], + "missing-spdx-in-file-docblock": [ + {"file": "lib/Repair/InitializeSettings.php", "rule": "no SPDX-License-Identifier in file docblock — pre-Conduction-template file with stale copyright (2024 Conduction)"}, + {"file": "lib/Listener/DeepLinkRegistrationListener.php", "rule": "no SPDX-License-Identifier in file docblock — pre-Conduction-template file with stale copyright (2024 Conduction)"} + ], + "stub-implementation": [ + {"file": "lib/Service/GitHubPushService.php", "rule": "Phase-1 implementation marked stub in file docblock: 'live HTTP calls land in a follow-up PR once knplabs/github-api is on the lockfile'. Affects REQ 'Export target — GitHub repository' — real GitHub push not wired."} + ], + "spec-purpose-stubbed": [ + {"file": "openspec/specs/*/spec.md", "rule": "Every spec file's Purpose section reads 'TBD - created by archiving change . Update Purpose after archive.' — archive process did not finalise Purpose text on any of the 17 specs"} + ] + } + }, + "ignored": 0, + "notes": [ + "Spec REQ format does NOT follow the scanner's documented `[A-Z]{2,4}-[0-9]+[a-z]*` pattern with bare REQ-IDs in headings. Instead the headings are `### Requirement: REQ-OBxxx-NNN ` (REQ-ID embedded after the colon) and the openbuilt-exporter spec omits REQ-IDs entirely, using `### Requirement: <Title>` only. The scanner treated REQ-ID-bearing headings as REQs with IDs as listed and REQ-ID-less headings (exporter spec) as REQs keyed by title. Both are usable but `/opsx-annotate` may need to handle the title-only form.", + "openbuilt-runtime/spec.md contains DUPLICATE REQ-IDs: REQ-OBR-006 appears three times (manifest endpoint, schema-designer routes, manifest 403), REQ-OBR-007 three times (schemas menu, draft-published indicator, app-list filter), REQ-OBR-008 twice, REQ-OBR-009 twice. This is the result of multiple archived changes each renumbering REQs in their deltas and the archive merge concatenating without de-duping. Annotations should disambiguate by capability+title; the JSON above uses suffixes like `(rbac-clone)` for clarity.", + "Every spec's Purpose section is the placeholder 'TBD - created by archiving change <name>. Update Purpose after archive.' — Bucket 4 flags this as 17 spec-level findings (spec-purpose-stubbed group).", + "Bucket 1 confidence is mostly high (≥ 0.85) because controller/service file docblocks already cite REQ IDs explicitly. The `(rbac-clone)` etc. suffixes on duplicate REQ-IDs mark methods whose primary capability differs from the runtime spec's reused ID — the annotate skill should pick the most specific capability (e.g. openbuilt-rbac REQ-OBRBAC-002 for the 403 branch, not openbuilt-runtime REQ-OBR-006-clone).", + "Frontend (src/) was NOT classified in this pass. Several specs (openbuilt-page-designer, openbuilt-schema-designer, application-detail-overview, full app-icon-management REQ-OBICON-004, openbuilt-rbac REQ-OBRBAC-004) are frontend-only and their Bucket 1 mapping lives in src/views/, src/components/, and src/composables/. Bucket 3b is over-counted because frontend implementations of those REQs exist but are out of scope.", + "openbuilt-runtime#REQ-OBR-004 (seeded hello-world Application) appears unimplemented in the live tree — green-field-migration (REQ-OBGFM-001) explicitly deletes pre-existing applications, and the wizard takes over creation. The legacy SeedHelloWorld repair step is in git history (removed-lines cache shows traces). Treat REQ-OBR-004 as superseded.", + "GitHubPushService is a documented Phase-1 stub. The exporter spec's 'Export target — GitHub repository' REQ is wire-protocol-locked but not actually executing live GitHub API calls. Bucket 4 flag captures the stub-status; a separate ghost change should track the live-wire work.", + "ApplicationsController has a very wide method surface (~30 methods) covering five distinct capabilities (runtime/manifest, version-snapshots/diff, rbac/list, templates/clone, plus version-routing helpers). Annotation should land per-method, not per-file." + ] +} diff --git a/openspec/coverage-report.md b/openspec/coverage-report.md new file mode 100644 index 00000000..4a03ff80 --- /dev/null +++ b/openspec/coverage-report.md @@ -0,0 +1,398 @@ +# Coverage Report — openbuilt + +Generated: 2026-05-24 00:00 UTC +Branch: development +Scanner: opsx-coverage-scan v1 + +## Summary + +| Bucket | Count | Next action | +|---|---|---| +| annotated | 0 | — (no `@spec openspec/changes/` tags in the tree) | +| plumbing | 29 | — (never tagged) | +| 1 — REQ matched | 185 | `/opsx-annotate openbuilt` | +| 2a — existing capability, no REQ | 17 (2 clusters) | `/opsx-reverse-spec openbuilt --extend <cap>` | +| 2b — no capability owner | 1 (1 cluster) | `/opsx-reverse-spec openbuilt --cluster deep-link-registration` | +| 3a — REQ broken (code removed) | 0 | — | +| 3b — REQ never implemented (or out-of-scope here) | 22 | Mark deferred or confirm frontend coverage | +| 4 — ADR conformance | 12 findings across 4 rules | Follow-up issue | + +## Bucket 1 — Ready to annotate (via ghost change `retrofit-2026-05-24-annotate-openbuilt`) + +Note: the suggested annotation tag is `@spec openspec/changes/retrofit-2026-05-24-annotate-openbuilt/tasks.md#task-N` where the ghost change's tasks.md will carry one task per (capability, REQ) pair. + +### capability: openbuilt-runtime + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationsController.php | getManifest | REQ-OBR-001 (+ rbac-clone REQ-OBR-006) | 0.95 | file docblock + routes.php | +| lib/Controller/ApplicationsController.php | resolveApplicationBySlug | REQ-OBR-001 | 0.80 | Pass B inherit, multi-caller | +| lib/Controller/DashboardController.php | page | REQ-OBR-009 | 0.90 | file docblock cites REQ-OBR-009 | +| lib/Controller/DashboardController.php | catchAll | REQ-OBR-009 | 0.80 | SPA fall-through twin | +| lib/Controller/DashboardController.php | publishCurrentUserGroups | REQ-OBR-009 | 0.95 | name = REQ verb | +| lib/Mcp/OpenBuiltToolProvider.php | handleGetAppManifest | REQ-OBR-001 | 0.80 | MCP equivalent | + +### capability: version-routing + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationsController.php | resolveVersionedManifestResponse | REQ-OBVR-001 / REQ-OBVR-002 | 0.90 | dispatches to ManifestResolverService | +| lib/Service/ManifestResolverService.php | resolve | REQ-OBVR-002 / REQ-OBVR-003 | 0.95 | file docblock | +| lib/Service/ManifestResolverService.php | checkNonProductionAccess | REQ-OBVR-003 | 0.90 | Pass B inherit | +| lib/Service/ManifestResolverService.php | findApplicationBySlug | REQ-OBVR-002 | 0.80 | step 1 of two-step lookup | +| lib/Service/ManifestResolverService.php | resolveProductionManifest | REQ-OBVR-002 | 0.85 | Pass B inherit | +| lib/Service/ManifestResolverService.php | findVersionBySlug | REQ-OBVR-002 | 0.85 | step 2 | +| lib/Service/ManifestResolverService.php | findVersionBySlugFallback | REQ-OBVR-002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ManifestResolverService.php | findVersionByUuid | REQ-OBVR-002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ManifestResolverService.php | isCallerAuthorised | REQ-OBVR-003 | 0.80 | Pass B inherit | +| lib/Service/ManifestResolverService.php | bucketContainsUid | REQ-OBVR-003 | 0.70 NEEDS-REVIEW | Pass B inherit | + +### capability: openbuilt-version-snapshots + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationsController.php | diffVersions | REQ-OBV-005 | 0.95 | routes.php + file docblock | +| lib/Controller/ApplicationsController.php | resolveVersionBlob | REQ-OBV-005 | 0.80 | Pass B inherit | + +### capability: openbuilt-rbac + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationsController.php | listMine | REQ-OBRBAC-002 / REQ-OBRBAC-003 | 0.95 | file docblock + routes.php | +| lib/Controller/ApplicationsController.php | filterApplicationsByRole | REQ-OBRBAC-003 | 0.85 | name matches REQ scenario | +| lib/Controller/ApplicationsController.php | requirePermission | REQ-OBRBAC-002 / REQ-OBR-006-clone | 0.85 | Pass B inherit | +| lib/Controller/ApplicationsController.php | recordAdminBypass | REQ-OBRBAC-007 | 0.90 | EVENT_ADMIN_BYPASS audit | +| lib/Controller/ApplicationsController.php | getUserGroupIds | REQ-OBRBAC-002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationsController.php | collectAuthorisedGroups | REQ-OBRBAC-002 | 0.80 | Pass B inherit | +| lib/Controller/ApplicationsController.php | classifyPrincipal | REQ-OBRBAC-002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationVersionsController.php | requireRole | REQ-OBRBAC-002 | 0.80 | role-gate on CRUD | +| lib/Controller/ApplicationVersionsController.php | collectAuthorisedPrincipals | REQ-OBRBAC-002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationVersionsController.php | absorbPrincipalBucket | REQ-OBRBAC-002 | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationVersionsController.php | getUserGroupIds | REQ-OBRBAC-002 | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ExportsController.php | isAuthorisedForApplication | REQ-OBRBAC-002 | 0.80 | ADR-005 IDOR guard | +| lib/Controller/ExportsController.php | fallbackAuthoriseViaOrLookup | REQ-OBRBAC-002 | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Mcp/OpenBuiltToolProvider.php | handleListApps | REQ-OBRBAC-003 | 0.70 NEEDS-REVIEW | MCP equivalent of listMine | + +### capability: openbuilt-template-catalogue + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationsController.php | createFromTemplate | REQ-OBTC-004 / REQ-OBTC-005 | 0.95 | file docblock + routes.php | +| lib/Controller/ApplicationsController.php | resolveSharedContext | REQ-OBTC-004 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationsController.php | buildClonedManifest | REQ-OBTC-005 | 0.85 | Pass B inherit | +| lib/Controller/ApplicationsController.php | provisionPerAppArtifacts | REQ-OBTC-005 | 0.85 | Pass B inherit | +| lib/Controller/ApplicationsController.php | persistApplication | REQ-OBTC-004 | 0.80 | Pass B inherit | +| lib/Controller/ApplicationsController.php | validateCloneRequest | REQ-OBTC-004 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationsController.php | extractCompanionSchemas | REQ-OBTC-005 | 0.85 | Pass B inherit | +| lib/Controller/ApplicationsController.php | buildRewriteMap | REQ-OBTC-005 | 0.85 | Pass B inherit | +| lib/Controller/ApplicationsController.php | provisionPerAppRegister | REQ-OBTC-005 | 0.90 | maps to spec scenario | +| lib/Controller/ApplicationsController.php | findOrCreateRegister | REQ-OBTC-005 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationsController.php | extractRegisterOwner | REQ-OBTC-005 | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationsController.php | cloneCompanionSchemas | REQ-OBTC-005 | 0.90 | deep-copy per spec | +| lib/Controller/ApplicationsController.php | rewriteSchemaRefs | REQ-OBTC-005 | 0.85 | Pass B inherit | +| lib/Controller/ApplicationsController.php | lookupOne | REQ-OBTC-005 | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Repair/SeedApplicationTemplates.php | run | REQ-OBTC-002 | 0.95 | file docblock | +| lib/Repair/SeedApplicationTemplates.php | validateFixture | REQ-OBTC-009 | 0.90 | file docblock | +| lib/Repair/SeedApplicationTemplates.php | findBySlug | REQ-OBTC-002 | 0.75 NEEDS-REVIEW | idempotency guard | + +### capability: application-versions + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationVersionsController.php | index | REQ-OBV-107 | 0.95 | file docblock | +| lib/Controller/ApplicationVersionsController.php | show | REQ-OBV-107 | 0.95 | file docblock | +| lib/Controller/ApplicationVersionsController.php | create | REQ-OBV-107 | 0.95 | file docblock | +| lib/Controller/ApplicationVersionsController.php | update | REQ-OBV-107 | 0.95 | file docblock | +| lib/Controller/ApplicationVersionsController.php | destroy | REQ-OBV-108 | 0.95 | file docblock — strategy-aware delete | +| lib/Controller/ApplicationVersionsController.php | loadApplication | REQ-OBV-107 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/ApplicationVersionsController.php | findVersionForApplication | REQ-OBV-107 | 0.80 | Pass B inherit | +| lib/Service/ApplicationVersionService.php | canonicaliseManifest | REQ-OBV-103 | 0.90 | SHA-256 diff source | +| lib/Service/ApplicationVersionService.php | hashManifest | REQ-OBV-103 | 0.90 | SHA-256 diff source | +| lib/Service/ApplicationVersionService.php | bumpPatch | REQ-OBV-103 | 0.95 | name = REQ wording | +| lib/Service/ApplicationVersionService.php | onSave | REQ-OBV-103 | 0.85 | auto-bump hook | +| lib/Service/ApplicationVersionService.php | guardNoCycle | REQ-OBV-104 | 0.95 | name = REQ wording | +| lib/Service/ApplicationVersionService.php | deleteVersion | REQ-OBV-108 | 0.95 | strategy-aware delete | +| lib/Service/ApplicationVersionService.php | assertValidStrategy | REQ-OBV-108 | 0.85 | Pass B inherit | +| lib/Service/ApplicationVersionService.php | assertNotProductionVersion | REQ-OBV-108 | 0.85 | Pass B inherit | +| lib/Service/ApplicationVersionService.php | dropPerVersionRegister | REQ-OBV-108 | 0.85 | delete-now strategy | +| lib/Service/ApplicationVersionService.php | flagRegisterOrphaned | REQ-OBV-108 | 0.80 | orphan-grace strategy | +| lib/Service/ApplicationVersionService.php | resolveNextPromotesTo | REQ-OBV-108 | 0.75 NEEDS-REVIEW | Pass B inherit | + +### capability: openbuilt-application-register + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Service/ApplicationVersionService.php | guardProductionVersionOwnership | REQ-OBA-008 | 0.90 | listener docblock | +| lib/Listener/ProductionVersionGuardListener.php | handle | REQ-OBA-008 / REQ-OBV-105 | 0.95 | file docblock | +| lib/Listener/ProductionVersionGuardListener.php | extractSchemaSlug | REQ-OBA-008 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Listener/ProductionVersionGuardListener.php | extractObjectData | REQ-OBA-008 | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Listener/ProductionVersionGuardListener.php | extractUuid | REQ-OBA-008 | 0.65 NEEDS-REVIEW | Pass B inherit | +| lib/Repair/PopulateApplicationPermissions.php | run | REQ-OBA-007 | 0.95 | file docblock | +| lib/Repair/PopulateApplicationPermissions.php | needsMigration | REQ-OBA-007 | 0.80 | Pass B inherit | + +### capability: application-creation-wizard + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationCreationController.php | wizard | REQ-OBWIZ-001 / 007 / 010 | 0.95 | file docblock | +| lib/Controller/ApplicationCreationController.php | collectPayload | REQ-OBWIZ-001 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ApplicationCreationService.php | createApplication | REQ-OBWIZ-007/008/009/010 | 0.95 | file docblock + flow comment | +| lib/Service/ApplicationCreationService.php | validatePayload | REQ-OBWIZ-005 / 006 | 0.90 | slug + chain validation | +| lib/Service/ApplicationCreationService.php | resolveVersionChain | REQ-OBWIZ-002 / 006 | 0.85 | REQ scenario noun match | +| lib/Service/ApplicationCreationService.php | appSlugExists | REQ-OBWIZ-005 | 0.80 | Pass B inherit | +| lib/Service/ApplicationCreationService.php | provisionRegister | REQ-OBWIZ-008 | 0.90 | per-version register | +| lib/Service/ApplicationCreationService.php | deleteRegister | REQ-OBWIZ-007 | 0.80 | rollback helper | +| lib/Service/ApplicationCreationService.php | rollback | REQ-OBWIZ-007 | 0.95 | name = REQ wording | +| lib/Service/ApplicationCreationService.php | loadDefaultManifest | REQ-OBWIZ-009 | 0.80 | initial manifest | +| lib/Service/ApplicationCreationService.php | loadDefaultSchemas | REQ-OBWIZ-008 | 0.80 | seed schema set | +| lib/Service/ApplicationCreationService.php | substituteRegisterSlug | REQ-OBWIZ-008 | 0.80 | Pass B inherit | +| lib/Service/ApplicationCreationService.php | substituteVersionContext | REQ-OBWIZ-009 | 0.80 | Pass B inherit | +| lib/Service/SlugValidator.php | validateAppSlug | REQ-OBWIZ-005 | 0.95 | file docblock | +| lib/Service/SlugValidator.php | validateVersionSlug | REQ-OBWIZ-005 | 0.95 | file docblock | +| lib/Service/SlugValidator.php | validateChainSlugs | REQ-OBWIZ-006 | 0.95 | file docblock | +| lib/Mcp/OpenBuiltToolProvider.php | handleCreateApp | REQ-OBWIZ-001 | 0.80 | MCP surface | + +### capability: application-insights + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ApplicationInsightsController.php | getInsights | REQ-OBAI-001 / 006 / 002 | 0.95 | file + method docblock | +| lib/Service/ApplicationInsightsService.php | requireAuthorisedCaller | REQ-OBAI-002 | 0.95 | file docblock | +| lib/Service/ApplicationInsightsService.php | computeInsights | REQ-OBAI-001 / 004 / 005 | 0.95 | file docblock | +| lib/Service/ApplicationInsightsService.php | deriveSchemaIds | REQ-OBAI-003 | 0.95 | file docblock | +| lib/Service/ApplicationInsightsService.php | extractSchemaIdForRegister | REQ-OBAI-003 | 0.80 | Pass B inherit | +| lib/Service/ApplicationInsightsService.php | isAuthorised | REQ-OBAI-002 | 0.80 | Pass B inherit | +| lib/Service/ApplicationInsightsService.php | callerInAnyRole | REQ-OBAI-002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ApplicationInsightsService.php | safeDistinctActorCount | REQ-OBAI-004 | 0.85 | Active-users KPI | +| lib/Service/ApplicationInsightsService.php | countObjects | REQ-OBAI-004 | 0.85 | Object-count KPI | +| lib/Service/ApplicationInsightsService.php | countAttachedFiles | REQ-OBAI-004 | 0.85 | Files KPI | +| lib/Service/ApplicationInsightsService.php | countAuditEvents | REQ-OBAI-004 | 0.85 | Audit-events KPI | +| lib/Service/ApplicationInsightsService.php | buildActivityTimeline | REQ-OBAI-005 | 0.90 | Activity payload | +| lib/Service/ApplicationInsightsService.php | sumChartSeries | REQ-OBAI-005 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ApplicationInsightsService.php | mergeChartIntoBuckets | REQ-OBAI-005 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ApplicationInsightsService.php | mergeSeriesData | REQ-OBAI-005 | 0.75 NEEDS-REVIEW | Pass B inherit | + +### capability: version-promotion + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/VersionPromotionController.php | promote | REQ-OBVP-001 / 007 | 0.95 | file docblock | +| lib/Controller/VersionPromotionController.php | mapExceptionToResponse | REQ-OBVP-006 / 009 | 0.80 | Pass B inherit | +| lib/Controller/VersionPromotionController.php | buildLockedResponse | REQ-OBVP-006 | 0.85 | 409 lock-contention | +| lib/Controller/VersionPromotionController.php | loadApplication | REQ-OBVP-001 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/VersionPromotionController.php | loadVersion | REQ-OBVP-001 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Controller/VersionPromotionController.php | assertEditorOrOwner | REQ-OBVP-007 | 0.90 | name = REQ wording | +| lib/Service/VersionPromotionService.php | defaultStrategyFor | REQ-OBVP-011 | 0.95 | name = REQ | +| lib/Service/VersionPromotionService.php | promote | REQ-OBVP-001 | 0.95 | top-level entry | +| lib/Service/VersionPromotionService.php | runStrategy | REQ-OBVP-002 / 003 / 004 | 0.90 | Pass B inherit | +| lib/Service/VersionPromotionService.php | runStartWithSourceData | REQ-OBVP-002 | 0.95 | name = REQ | +| lib/Service/VersionPromotionService.php | runMigrateExistingData | REQ-OBVP-003 | 0.95 | name = REQ | +| lib/Service/VersionPromotionService.php | runEmptyStart | REQ-OBVP-004 | 0.95 | name = REQ | +| lib/Service/VersionPromotionService.php | forwardSchemaSetToOR | REQ-OBVP-005 | 0.90 | schema-diff deferred to OR | +| lib/Service/VersionPromotionService.php | wipeTargetRegister | REQ-OBVP-002 / 004 | 0.85 | Pass B inherit | +| lib/Service/VersionPromotionService.php | copyRowsFromSource | REQ-OBVP-002 | 0.85 | Pass B inherit | +| lib/Service/VersionPromotionService.php | applyManifestAndSemver | REQ-OBVP-008 | 0.95 | name = REQ wording | +| lib/Service/VersionPromotionService.php | handlePromotionFailure | REQ-OBVP-009 | 0.95 | archive flip per REQ | +| lib/Service/VersionPromotionService.php | acquireLock | REQ-OBVP-006 | 0.95 | OR object lock | +| lib/Service/VersionPromotionService.php | releaseLock | REQ-OBVP-006 | 0.90 | Pass B inherit | +| lib/Service/VersionPromotionService.php | callGetLockInfo | REQ-OBVP-006 | 0.80 | Pass B inherit | +| lib/Mcp/OpenBuiltToolProvider.php | handlePromoteVersion | REQ-OBVP-001 | 0.80 | MCP surface | + +### capability: app-icon-management + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/IconController.php | iconLight | REQ-OBICON-002 | 0.95 | docblocks + routes.php | +| lib/Controller/IconController.php | iconDark | REQ-OBICON-003 | 0.95 | docblocks + routes.php | +| lib/Controller/IconController.php | buildIconResponse | REQ-OBICON-002 / 003 | 0.85 | Pass B inherit | +| lib/Service/IconService.php | getIconStream | REQ-OBICON-002 / 003 | 0.95 | file docblock | +| lib/Service/IconService.php | fetchApplication | REQ-OBICON-001 | 0.80 | Pass B inherit | +| lib/Service/IconService.php | resolveIconLight | REQ-OBICON-002 | 0.85 | Pass B inherit | +| lib/Service/IconService.php | resolveIconDark | REQ-OBICON-003 | 0.85 | Pass B inherit | +| lib/Service/IconService.php | streamForIconField | REQ-OBICON-001 / 002 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/IconService.php | fetchAttachedFileStream | REQ-OBICON-001 | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/IconService.php | fallbackStream | REQ-OBICON-002 | 0.75 NEEDS-REVIEW | Pass B inherit | + +### capability: app-nav-entries + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Service/AppNavigationService.php | registerNavEntries | REQ-OBNAV-001 | 0.95 | name = REQ verb | +| lib/Service/AppNavigationService.php | isVisibleForCurrentUser | REQ-OBNAV-002 / 003 | 0.95 | docblock lists four rules | +| lib/Service/AppNavigationService.php | flattenPermissions | REQ-OBNAV-002 | 0.80 | Pass B inherit | +| lib/Service/AppNavigationService.php | principalsMatchGroups | REQ-OBNAV-002 | 0.80 | Pass B inherit | +| lib/Service/AppNavigationService.php | getPublishedApplications | REQ-OBNAV-001 / 004 | 0.85 | per-request fetch | + +### capability: openbuilt-exporter + +| File | Method | REQ (by title) | Confidence | Signal | +|---|---|---|---|---| +| lib/Controller/ExportsController.php | submit | "Export is asynchronous via Nextcloud's IJob" | 0.90 | route shape matches spec | +| lib/Controller/ExportsController.php | download | "Export target — ZIP archive" | 0.90 | route shape matches spec | +| lib/Controller/ExportsController.php | isAuthorisedForJob | "ExportJob schema declaration" | 0.70 NEEDS-REVIEW | per-job auth | +| lib/Controller/ExportsController.php | validateSubmitBody | "ExportJob schema declaration" | 0.80 | body validation per schema | +| lib/Controller/ExportsController.php | validateGithubFields | "Export target — GitHub repository" | 0.85 | GitHub-specific fields | +| lib/Service/ExportService.php | generateAppZip | "Exported tree shape conforms to the nextcloud-app-template baseline" | 0.90 | ZIP generation | +| lib/Service/ExportService.php | packageZip | "Export target — ZIP archive" | 0.90 | Pass B inherit | +| lib/Service/ExportService.php | listFilesSorted | "Re-exports are idempotent" | 0.80 | byte-equivalence support | +| lib/Service/ExportService.php | resolvePlaceholders | "Exported tree shape conforms to the nextcloud-app-template baseline" | 0.90 | matches spec scenario | +| lib/Service/ExportService.php | isBinary | "Exported tree shape ... baseline" | 0.70 NEEDS-REVIEW | binary-skip helper | +| lib/Service/ExportService.php | copyTemplate | "Exported tree shape ... baseline" | 0.85 | copies lib/Resources/template | +| lib/Service/ExportService.php | prepareScratchDir | "Export target — ZIP archive" | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/Service/ExportService.php | getOrCreateAppDataDir | "Export target — ZIP archive" | 0.80 | appdata storage | +| lib/Service/ExportService.php | rrmdir | "Export target — ZIP archive" | 0.70 NEEDS-REVIEW | scratch cleanup | +| lib/Service/ExportJobService.php | queue | "Export is asynchronous via Nextcloud's IJob" | 0.95 | IJobList | +| lib/Service/ExportJobService.php | persistJob | "ExportJob schema declaration" | 0.85 | OR record | +| lib/Service/ExportJobService.php | transitionJob | "ExportJob schema declaration (lifecycle)" | 0.90 | queued→running→succeeded\|failed | +| lib/Service/ExportJobService.php | mergeJobFields | "ExportJob schema declaration" | 0.80 | Pass B inherit | +| lib/Service/ExportJobService.php | resolveDownload | "Export target — ZIP archive" | 0.90 | downloadUrl + expiry | +| lib/Service/ExportJobService.php | fetchPat | "Export target — GitHub repository" | 0.95 | ICredentialsManager | +| lib/Service/ExportJobService.php | clearPat | "Export target — GitHub repository" | 0.95 | PAT wipe on terminal | +| lib/Service/ExportJobService.php | credentialKey | "Export target — GitHub repository" | 0.80 | Pass B inherit | +| lib/Service/ExportJobService.php | uuid4 | "ExportJob schema declaration" | 0.65 NEEDS-REVIEW | UUID helper | +| lib/Service/GitHubPushService.php | push | "Export target — GitHub repository" | 0.95 | file docblock; STUB (Bucket 4) | +| lib/Service/GitHubPushService.php | resolveDefaultBranch | "Export target — GitHub repository" | 0.85 | PR default-branch | +| lib/Service/PlaceholderResolver.php | buildMap | "Exported tree shape ... baseline" | 0.85 | substitution map | +| lib/Service/PlaceholderResolver.php | resolve | "Exported tree shape ... baseline" | 0.85 | string resolver | +| lib/Service/PlaceholderResolver.php | slug | "Exported tree shape ... baseline" | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/Service/PlaceholderResolver.php | pascalCase | "Exported tree shape ... baseline" | 0.70 NEEDS-REVIEW | namespace casing | +| lib/BackgroundJob/RunExportJob.php | run | "Export is asynchronous via Nextcloud's IJob" | 0.95 | implements IJob | +| lib/BackgroundJob/RunExportJob.php | extractJobUuid | "Export is asynchronous via Nextcloud's IJob" | 0.70 NEEDS-REVIEW | Pass B inherit | +| lib/BackgroundJob/RunExportJob.php | executePipeline | "Export is asynchronous via Nextcloud's IJob" | 0.85 | pipeline drive | +| lib/BackgroundJob/RunExportJob.php | maybePush | "Export target — GitHub repository" | 0.85 | invokes GitHubPushService | +| lib/BackgroundJob/RunExportJob.php | buildSuccessFields | "ExportJob schema declaration" | 0.75 NEEDS-REVIEW | Pass B inherit | +| lib/BackgroundJob/CleanupExpiredExports.php | run | "Export target — ZIP archive (24h expiry + purge)" | 0.95 | file docblock | + +### capability: green-field-migration + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Repair/MigrateToVersionedModel.php | run | REQ-OBGFM-001 | 0.95 | @destructive + docblock | +| lib/Repair/MigrateToVersionedModel.php | isAlreadyVersioned | REQ-OBGFM-002 | 0.95 | docblock cites REQ | +| lib/Repair/MigrateToVersionedModel.php | enumerateApplications | REQ-OBGFM-001 | 0.80 | Pass B inherit | +| lib/Repair/MigrateToVersionedModel.php | migrateOne | REQ-OBGFM-003 / 004 | 0.85 | per-app log + register-delete | + +### capability: openbuilt-schema-designer / openbuilt-page-designer (MCP surface) + +| File | Method | REQ | Confidence | Signal | +|---|---|---|---|---| +| lib/Mcp/OpenBuiltToolProvider.php | handleUpsertSchema | REQ-OBSD-006 | 0.75 NEEDS-REVIEW | MCP surface for schema save | +| lib/Mcp/OpenBuiltToolProvider.php | handleUpsertPage | REQ-OBPD-009 | 0.75 NEEDS-REVIEW | MCP surface for page save | +| lib/Mcp/OpenBuiltToolProvider.php | handleAddWidget | REQ-OBPD-004 | 0.70 NEEDS-REVIEW | widget add path | +| lib/Mcp/OpenBuiltToolProvider.php | handleUpsertMenuItem | REQ-OBPD-001 | 0.80 | menu-tree editor | + +## Bucket 2a — Existing capability, no REQ (reverse-spec --extend) + +### cluster: openbuilt-runtime (16 methods) + +The MCP tool provider exposes the OpenBuilt authoring surface to LLMs (per ADR-019 integration registry). None of the openbuilt-runtime REQs mention an MCP tool surface — they describe the HTTP / Vue runtime. Recommend `/opsx-reverse-spec openbuilt --extend openbuilt-runtime` to add 1–2 REQs covering "MCP tool surface exposing CRUD + promotion + manifest mutations". + +- lib/Mcp/OpenBuiltToolProvider.php::getAppId / getTools / invokeTool — MCP catalogue + dispatcher +- lib/Mcp/OpenBuiltToolProvider.php::loadVersion / saveVersionManifest — internal version IO +- lib/Mcp/OpenBuiltToolProvider.php::validateListAppsArgs / resolveApplicationBySlug / mapApplication / sourceDescriptor / errorResult — MCP-internal plumbing +- lib/Mcp/OpenBuiltToolProvider.php::requireAuthenticatedUser / isAdmin / isValidSlug / buildDeepLink / toArray / extractUuid — guards + helpers + +### cluster: app-icon-management (1 method) + +- lib/Service/IconService.php::extractUuid — observed: UUID accessor on Application row, used internally for OR-file lookup. No scenario coverage in current REQs. + +## Bucket 2b — No capability owner (reverse-spec --cluster) + +### cluster: deep-link-registration (1 method) + +- lib/Listener/DeepLinkRegistrationListener.php::handle — observed: registers OpenBuilt deep-link URL patterns with OpenRegister's search provider on the `DeepLinkRegistrationEvent`. No openbuilt spec covers integration-registry / deep-link patterns. Closest concept is ADR-019, but no REQ names this concrete surface. + +Recommended: `/opsx-reverse-spec openbuilt --cluster deep-link-registration` to author a small spec (1–2 REQs) for the deep-link contract. + +## Bucket 3 — Surfaced for human triage + +### 3a — possibly broken (0) + +None. The reverse-pass keyword scan against `/tmp/removed-lines-openbuilt.txt` (5,249 lines) did not surface any REQ whose keywords appear ≥ 2 times in removed lines with strong specificity. Code historically removed (e.g. SeedHelloWorld) is intentionally superseded. + +### 3b — never implemented OR out-of-scope (22) + +The current pass is PHP-only. Many "unimplemented" REQs are in fact frontend implementations — listed here for visibility, but they are NOT genuinely missing. + +Genuinely declarative / no PHP method expected: +- openbuilt-application-register REQ-OBA-001, REQ-OBA-002, REQ-OBA-003, REQ-OBA-004, REQ-OBA-005, REQ-OBA-006 — schema / lifecycle declarations in `lib/Settings/openbuilt_register.json` +- application-versions REQ-OBV-101, REQ-OBV-102, REQ-OBV-106 — schema declarations + OR-side lifecycle +- openbuilt-rbac REQ-OBRBAC-001 — declarative schema (PopulateApplicationPermissions covers migration only) + +Frontend implementations (out of scope for this pass): +- openbuilt-runtime REQ-OBR-002, 003, 005, 007 (Schemas menu), 008 (VersionHistory.vue), 009 (Rollback), 010 (ManifestDiff.vue), 013 (ApplicationCard) +- openbuilt-page-designer REQ-OBPD-001 through REQ-OBPD-011 +- openbuilt-schema-designer REQ-OBSD-001 through REQ-OBSD-008 +- application-detail-overview REQ-OBADO-001 through REQ-OBADO-012 +- version-routing REQ-OBVR-004 through REQ-OBVR-009 +- openbuilt-version-snapshots REQ-OBV-002 (lifecycle), REQ-OBV-003 (rollback frontend) +- openbuilt-template-catalogue REQ-OBTC-001, 003, 006, 007, 008, 010 +- openbuilt-rbac REQ-OBRBAC-004 (useRole composable) +- openbuilt-runtime REQ-OBR-007 (draft-published indicator) + +Possibly absent / superseded: +- openbuilt-runtime REQ-OBR-004 — seeded hello-world Application. Superseded by green-field-migration + the creation wizard; no SeedHelloWorld repair step in the live tree. +- openbuilt-runtime REQ-OBR-006 (Application editor exposes a Publish action) — frontend; if not in src/views, treat as missing. +- openbuilt-rbac REQ-OBRBAC-005 (Transfer-ownership flow) — no PHP controller endpoint found; verify whether frontend PUTs directly via OR REST. +- openbuilt-rbac REQ-OBRBAC-006 (Global `openbuilt.use` nav-entry permission) — the `group:*` sentinel handling lives in AppNavigationService (REQ-OBNAV-003); standalone REQ-OBRBAC-006 wording is not separately implemented. Likely the same concern under two REQ headings. + +## Bucket 4 — ADR conformance findings + +### missing-spec-in-file-docblock (8 files) + +Every PHP file under lib/ should carry `@spec openspec/changes/{change}/tasks.md#task-N` per ADR-003 §Spec traceability. None currently do. + +- lib/Repair/InitializeSettings.php +- lib/Listener/DeepLinkRegistrationListener.php +- lib/Controller/SettingsController.php +- lib/Controller/HealthController.php +- lib/Controller/MetricsController.php +- lib/Controller/DashboardController.php +- lib/Sections/SettingsSection.php +- lib/Settings/AdminSettings.php + +The other ~35 PHP files cite REQ IDs in their file docblocks but as free-text ("REQ-OBV-107"), not in the `@spec openspec/changes/...` form required by ADR-003. The `/opsx-annotate` step will add the canonical `@spec` tags pointing at the ghost change's tasks.md. + +### missing-spdx-in-file-docblock (2 files) + +- lib/Repair/InitializeSettings.php — stale 2024 Conduction copyright, no SPDX +- lib/Listener/DeepLinkRegistrationListener.php — stale 2024 Conduction copyright, no SPDX + +Both are pre-template files. Fix: copy the canonical SPDX block (already used by the other 41 PHP files) and bump copyright to 2026. + +### stub-implementation (1 file) + +- lib/Service/GitHubPushService.php — file docblock states "Phase-1 implementation: stubbed. The wire-protocol contract is locked in (signatures + PAT handling); the live HTTP calls land in a follow-up PR once `knplabs/github-api` is on the lockfile." Affects exporter REQ "Export target — GitHub repository" — the GitHub path will not actually push to GitHub at runtime. Suggest filing a tracking issue for the live-wire follow-up. + +### spec-purpose-stubbed (17 specs) + +Every spec file's `## Purpose` section reads "TBD - created by archiving change <name>. Update Purpose after archive." — the archive step didn't update Purpose on any of the 17 specs. + +- openspec/specs/app-icon-management/spec.md +- openspec/specs/app-nav-entries/spec.md +- openspec/specs/application-creation-wizard/spec.md +- openspec/specs/application-detail-overview/spec.md +- openspec/specs/application-insights/spec.md +- openspec/specs/application-versions/spec.md +- openspec/specs/green-field-migration/spec.md +- openspec/specs/openbuilt-application-register/spec.md +- openspec/specs/openbuilt-exporter/spec.md +- openspec/specs/openbuilt-page-designer/spec.md +- openspec/specs/openbuilt-rbac/spec.md +- openspec/specs/openbuilt-runtime/spec.md +- openspec/specs/openbuilt-schema-designer/spec.md +- openspec/specs/openbuilt-template-catalogue/spec.md +- openspec/specs/openbuilt-version-snapshots/spec.md +- openspec/specs/version-promotion/spec.md +- openspec/specs/version-routing/spec.md + +## Notes for the human reviewer + +- **Spec heading format mismatch.** The skill's documented REQ-ID pattern is `[A-Z]{2,4}-[0-9]+[a-z]*` (e.g. `REQ-001`, `ZRC-005b`). openbuilt specs use `### Requirement: REQ-OBxxx-NNN <Title>` (ID embedded in colon-prefixed title), and `openbuilt-exporter` specs use `### Requirement: <Title>` only (no REQ-ID). Both work as REQ headings but the annotate skill needs to handle title-only form (use `cap#title-slug` as a stable identifier). +- **Duplicate REQ-IDs in openbuilt-runtime.** REQ-OBR-006, REQ-OBR-007, REQ-OBR-008, REQ-OBR-009 each appear two or three times in the spec — each archived change reused the same numeric prefix and the archive merge concatenated without de-duping. The JSON annotates the clones with capability-prefix suffixes (e.g. `REQ-OBR-006(rbac-clone)`); a real fix is to renumber + redirect. +- **Frontend (src/) is not classified.** ~88 Vue/JS files implement large parts of openbuilt-page-designer, openbuilt-schema-designer, application-detail-overview, openbuilt-rbac (useRole), version-routing client helpers, and most of openbuilt-runtime. Bucket 3b is inflated by this — the next pass should add JS/Vue support to the scanner or run a separate frontend pass. +- **High overall confidence.** Because controllers + services already cite REQ IDs in their file docblocks, ~75% of Bucket 1 lands at ≥ 0.85 confidence. The 0.70–0.80 NEEDS-REVIEW entries are almost all private helpers inherited via Pass B; they're usually safe to annotate but worth a human eyeball. +- **Bucket 1 size (185 methods).** Above the skill's 150-method threshold. Recommend running `/opsx-annotate openbuilt --capability <cap>` one capability at a time when that flag lands; otherwise the annotate ghost change PR will be very wide. +- **GitHub push is a stub.** The exporter looks complete in shape, but the GitHub-target branch will not push live in this build. The wire-protocol contract is locked; the lockfile bump + live HTTP wiring is a real follow-up PR. +- **SeedHelloWorld is gone.** The openbuilt-runtime REQ-OBR-004 (seeded hello-world Application) was superseded by green-field-migration + the creation wizard. Either deprecate the REQ or rewrite it to describe SeedApplicationTemplates' role. +- **Reverse-pass cache.** `/tmp/removed-lines-openbuilt.txt` written; 5,249 lines covering full git history of lib/ + src/. No Bucket 3a hits — no REQs found with strong evidence of historical implementation followed by removal.