From 85261487fefc13a7dab3d3cce028fea5bcf00698 Mon Sep 17 00:00:00 2001 From: Al Gorithm Builder Date: Sat, 18 Apr 2026 20:36:09 +0000 Subject: [PATCH 01/11] feat: Add @spec PHPDoc tags to prometheus-metrics implementation (#212) --- lib/Controller/HealthController.php | 4 ++++ lib/Controller/MetricsController.php | 6 ++++++ tests/Unit/Controller/HealthControllerTest.php | 2 ++ tests/Unit/Controller/MetricsControllerTest.php | 2 ++ 4 files changed, 14 insertions(+) diff --git a/lib/Controller/HealthController.php b/lib/Controller/HealthController.php index 1d499218..787e4eaf 100644 --- a/lib/Controller/HealthController.php +++ b/lib/Controller/HealthController.php @@ -57,6 +57,8 @@ public function __construct( /** * Health check endpoint. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-4 + * * @NoCSRFRequired * * @return JSONResponse Health status @@ -126,6 +128,8 @@ private function checkDatabase(): string * OpenRegister is a hard dependency for Procest. If it is not enabled, * the overall health status MUST be "error". * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-4 + * * @return string 'ok' or error message */ private function checkOpenRegister(): string diff --git a/lib/Controller/MetricsController.php b/lib/Controller/MetricsController.php index de693618..327a1d3f 100644 --- a/lib/Controller/MetricsController.php +++ b/lib/Controller/MetricsController.php @@ -83,6 +83,10 @@ public function index(): TextPlainResponse /** * Collect all metrics and format as Prometheus text. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-1 + * @spec openspec/changes/prometheus-metrics/tasks.md#task-2 + * @spec openspec/changes/prometheus-metrics/tasks.md#task-3 + * * @return string Prometheus exposition format text */ private function collectMetrics(): string @@ -170,6 +174,8 @@ private function collectMetrics(): string * * Falls back to direct computation if APCu is unavailable. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-3 + * * @param string $key The cache key * @param int $ttl Cache TTL in seconds * @param callable $compute Callable that computes the value on cache miss diff --git a/tests/Unit/Controller/HealthControllerTest.php b/tests/Unit/Controller/HealthControllerTest.php index 439180f4..80612eb0 100644 --- a/tests/Unit/Controller/HealthControllerTest.php +++ b/tests/Unit/Controller/HealthControllerTest.php @@ -32,6 +32,8 @@ /** * Unit tests for the HealthController class. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-6 + * * @covers \OCA\Procest\Controller\HealthController */ class HealthControllerTest extends TestCase diff --git a/tests/Unit/Controller/MetricsControllerTest.php b/tests/Unit/Controller/MetricsControllerTest.php index 4aeee5a0..2345fe2a 100644 --- a/tests/Unit/Controller/MetricsControllerTest.php +++ b/tests/Unit/Controller/MetricsControllerTest.php @@ -31,6 +31,8 @@ /** * Unit tests for the MetricsController class. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-5 + * * @covers \OCA\Procest\Controller\MetricsController */ class MetricsControllerTest extends TestCase From 926e739612a2385d87a415d13edbab1a10b5e4a4 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 20 Apr 2026 18:30:04 +0200 Subject: [PATCH 02/11] chore(hydra): back-fill v2 hydra.json from historical logs --- .../changes/prometheus-metrics/hydra.json | 333 ++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 openspec/changes/prometheus-metrics/hydra.json diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json new file mode 100644 index 00000000..97476f7e --- /dev/null +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -0,0 +1,333 @@ +{ + "spec_slug": "prometheus-metrics", + "app": "procest", + "repo": "ConductionNL/procest", + "issue": 212, + "depends_on": [], + "schema_version": 2, + "cycles": [ + { + "cycle": 1, + "trigger": "build:queued", + "started_at": "2026-04-18T20:32:47Z", + "ended_at": null, + "outcome": "aborted", + "outcome_reason": "no terminal label seen in timeline", + "pattern_tags": [ + "reviewer-skipped-full-suite" + ], + "stages": [ + { + "stage": "build", + "persona": "Al Gorithm", + "model": "haiku", + "container": "hydra-builder", + "started_at": "2026-04-18T20:32:47Z", + "ended_at": "2026-04-18T20:32:48Z", + "turns_used": 53, + "turns_budget": 200, + "cost_usd": 0.3818, + "checks_run": [ + "composer check:strict" + ], + "checks_skipped": [], + "findings": [ + { + "id": "b-builder-build-0", + "severity": "WARNING", + "gate": null, + "rule": "builder output flagged failure language", + "status": "open", + "note": "Perfect! I have successfully implemented the prometheus-metrics OpenSpec change. Here's what was completed:\n\n## Implementation Summary\n\n**PR Created:** [#251 on ConductionNL/procest](https://github.com/ConductionNL/procest/pull/251)\n\n### Changes Made:\n\n1. **MetricsController Enhancements**\n - Added `nextcloud_version` label to `procest_info` gauge\n - Implemented `procest_cases_created_today` metric\n - Added `getCached()` method with APCu caching (30s TTL for standard queries, 60s for overd", + "autofixable": false + } + ], + "decisions": [], + "verdict": "fail" + }, + { + "stage": "pre-review-quality", + "persona": "orchestrator", + "container": "hydra-quality-runner", + "started_at": "2026-04-18T20:32:47Z", + "ended_at": "2026-04-18T20:32:48Z", + "exit_code": 0, + "checks_run": [ + "php-lint", + "phpcs", + "phpmd", + "psalm", + "phpstan", + "phpmetrics", + "composer-audit", + "spdx-headers", + "forbidden-patterns", + "eslint", + "stylelint", + "npm-audit", + "phpunit" + ], + "checks_skipped": [ + "publiccode", + "gitleaks", + "trivy", + "newman" + ], + "gates": { + "php-lint": { + "pass": true, + "failures": 0 + }, + "phpcs": { + "pass": true, + "failures": 0 + }, + "phpmd": { + "pass": true, + "failures": 0 + }, + "psalm": { + "pass": true, + "failures": 0 + }, + "phpstan": { + "pass": true, + "failures": 0 + }, + "phpmetrics": { + "pass": true, + "failures": 0 + }, + "composer-audit": { + "pass": true, + "failures": 0 + }, + "spdx-headers": { + "pass": true, + "failures": 0 + }, + "forbidden-patterns": { + "pass": true, + "failures": 0 + }, + "eslint": { + "pass": true, + "failures": 0 + }, + "stylelint": { + "pass": true, + "failures": 0 + }, + "npm-audit": { + "pass": true, + "failures": 0 + }, + "phpunit": { + "pass": true, + "failures": 0 + } + }, + "findings": [], + "verdict": "pass" + }, + { + "stage": "code-review", + "persona": "Juan Claude van Damme", + "model": "sonnet", + "container": "hydra-reviewer", + "started_at": "2026-04-18T20:41:58Z", + "ended_at": "2026-04-18T20:41:59Z", + "turns_used": 41, + "turns_budget": 40, + "cost_usd": 1.254, + "checks_run": [ + "composer check:strict", + "phpcs" + ], + "checks_skipped": [ + "hydra-gates" + ], + "findings": [], + "verdict": "none" + }, + { + "stage": "security-review", + "persona": "Clyde Barcode", + "model": "sonnet", + "container": "hydra-security", + "started_at": "2026-04-18T20:48:55Z", + "ended_at": "2026-04-18T20:48:56Z", + "turns_used": 34, + "turns_budget": 40, + "cost_usd": 0.8284, + "checks_run": [], + "checks_skipped": [ + "hydra-gates", + "composer check:strict" + ], + "findings": [ + { + "id": "SEC-01", + "severity": "CRITICAL", + "gate": "OWASP A01:2021", + "file": "lib/Controller/HealthController.php", + "line": 66, + "rule": "OWASP A01:2021", + "status": "open", + "note": "Without @PublicPage, Nextcloud auth middleware blocks all unauthenticated requests. Container-orchestration health probes receive a 302 redirect to the login page, making the endpoint non-functional. Fix: add @PublicPage above @NoCSRFRequired in the method docblock. Could not apply: container has no write access to repository files (root-owned).", + "autofixable": false + }, + { + "id": "SEC-02", + "severity": "CRITICAL", + "gate": "OWASP A01:2021", + "file": "lib/Controller/MetricsController.php", + "line": 74, + "rule": "OWASP A01:2021", + "status": "open", + "note": "Without @PublicPage, Nextcloud auth middleware blocks all Prometheus scrape requests. The entire /api/metrics endpoint is non-functional for unauthenticated scrapers. Fix: add @PublicPage above @NoCSRFRequired. Could not apply: container has no write access to repository files (root-owned).", + "autofixable": false + }, + { + "id": "SEC-03", + "severity": "WARNING", + "gate": "CWE-209 / OWASP A05:2021", + "file": "lib/Controller/HealthController.php", + "line": 121, + "rule": "CWE-209 / OWASP A05:2021", + "status": "open", + "note": "checkDatabase() (line 121), checkOpenRegister() (line 145), and checkFilesystem() (line 167) return $e->getMessage() verbatim in the response body. Once @PublicPage is applied, any unauthenticated caller can observe DB connection-string fragments, table names, or file paths. Fix: return generic strings ('failed: database unavailable', etc.) and ensure $this->logger records the full error. checkFilesystem catch block is also missing a logger call. Could not apply: container has no write access to", + "autofixable": false + } + ], + "verdict": "fail" + } + ] + }, + { + "cycle": 2, + "trigger": "build:queued", + "started_at": "2026-04-18T20:53:35Z", + "ended_at": null, + "outcome": "aborted", + "outcome_reason": "no terminal label seen in timeline", + "pattern_tags": [], + "stages": [ + { + "stage": "quality-recheck", + "persona": "orchestrator", + "container": "hydra-quality-runner", + "started_at": "2026-04-18T20:53:35Z", + "ended_at": "2026-04-18T20:53:36Z", + "exit_code": 1, + "checks_run": [ + "phpcs", + "phpmd", + "psalm", + "phpstan", + "phpmetrics", + "composer-audit", + "spdx-headers", + "forbidden-patterns", + "eslint", + "npm-audit", + "phpunit" + ], + "checks_skipped": [ + "php-lint", + "publiccode", + "stylelint", + "gitleaks", + "trivy", + "newman" + ], + "gates": { + "phpcs": { + "pass": false, + "failures": 1 + }, + "phpmd": { + "pass": true, + "failures": 0 + }, + "psalm": { + "pass": true, + "failures": 0 + }, + "phpstan": { + "pass": true, + "failures": 0 + }, + "phpmetrics": { + "pass": true, + "failures": 0 + }, + "composer-audit": { + "pass": true, + "failures": 0 + }, + "spdx-headers": { + "pass": true, + "failures": 0 + }, + "forbidden-patterns": { + "pass": false, + "failures": 1 + }, + "eslint": { + "pass": false, + "failures": 1 + }, + "npm-audit": { + "pass": true, + "failures": 0 + }, + "phpunit": { + "pass": false, + "failures": 1 + } + }, + "findings": [ + { + "id": "qr-quality-recheck-phpcs", + "severity": "CRITICAL", + "gate": "phpcs", + "rule": "phpcs gate failing", + "status": "open", + "note": "phpcs reported status=fail in quality-recheck.json", + "autofixable": true + }, + { + "id": "qr-quality-recheck-forbidden-patterns", + "severity": "CRITICAL", + "gate": "forbidden-patterns", + "rule": "forbidden-patterns gate failing", + "status": "open", + "note": "forbidden-patterns reported status=fail in quality-recheck.json", + "autofixable": true + }, + { + "id": "qr-quality-recheck-eslint", + "severity": "CRITICAL", + "gate": "eslint", + "rule": "eslint gate failing", + "status": "open", + "note": "eslint reported status=fail in quality-recheck.json", + "autofixable": true + }, + { + "id": "qr-quality-recheck-phpunit", + "severity": "CRITICAL", + "gate": "phpunit", + "rule": "phpunit gate failing", + "status": "open", + "note": "phpunit reported status=fail in quality-recheck.json", + "autofixable": false + } + ], + "verdict": "fail" + } + ] + } + ] +} From b1d5b2416493e576a459e96db4fb2c9532c2276c Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Mon, 20 Apr 2026 23:52:50 +0200 Subject: [PATCH 03/11] chore(hydra): close cycle outcome=aborted [skip ci] --- openspec/changes/prometheus-metrics/hydra.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index 97476f7e..e912b9d6 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -208,9 +208,9 @@ "cycle": 2, "trigger": "build:queued", "started_at": "2026-04-18T20:53:35Z", - "ended_at": null, + "ended_at": "2026-04-20T21:52:50Z", "outcome": "aborted", - "outcome_reason": "no terminal label seen in timeline", + "outcome_reason": "rebuild:queued \u2014 human wiped prior cycle", "pattern_tags": [], "stages": [ { From 767159596ef3b308f1cf926e31115580e41453d8 Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 06:44:27 +0200 Subject: [PATCH 04/11] chore(hydra): init cycle (trigger=build:queued) [skip ci] --- openspec/changes/prometheus-metrics/hydra.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index e912b9d6..ce34c568 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -328,6 +328,16 @@ "verdict": "fail" } ] + }, + { + "cycle": 3, + "trigger": "build:queued", + "started_at": "2026-04-21T04:44:27Z", + "ended_at": null, + "outcome": "in-flight", + "outcome_reason": null, + "pattern_tags": [], + "stages": [] } ] } From d6622b2aade3f02398cb6b23615c6b60052dbf78 Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 06:44:32 +0200 Subject: [PATCH 05/11] chore(hydra): record build stage [skip ci] --- .../changes/prometheus-metrics/hydra.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index ce34c568..9ab92ba1 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -337,7 +337,24 @@ "outcome": "in-flight", "outcome_reason": null, "pattern_tags": [], - "stages": [] + "stages": [ + { + "stage": "build", + "persona": "Al Gorithm", + "model": "haiku", + "container": "hydra-builder", + "started_at": "2026-04-20T21:53:32Z", + "ended_at": "2026-04-21T04:44:24Z", + "exit_code": 0, + "turns_used": 125, + "turns_budget": 40, + "checks_run": [], + "checks_skipped": [], + "findings": [], + "decisions": [], + "verdict": "pass" + } + ] } ] } From 05f29034ae02db88af972d78ffd9b762839c4df7 Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 06:55:38 +0200 Subject: [PATCH 06/11] chore(hydra): pattern-tag browser-test-nc-setup-failed [skip ci] --- openspec/changes/prometheus-metrics/hydra.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index 9ab92ba1..34b22cfd 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -336,7 +336,9 @@ "ended_at": null, "outcome": "in-flight", "outcome_reason": null, - "pattern_tags": [], + "pattern_tags": [ + "browser-test-nc-setup-failed" + ], "stages": [ { "stage": "build", From 235b3bd0ed3dd01fcf04609494f20ece410e99c7 Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 09:27:09 +0200 Subject: [PATCH 07/11] chore(hydra): record pre-review-quality stage [skip ci] --- .../changes/prometheus-metrics/hydra.json | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index 34b22cfd..4d7009a0 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -355,6 +355,86 @@ "findings": [], "decisions": [], "verdict": "pass" + }, + { + "stage": "pre-review-quality", + "persona": "orchestrator", + "container": "hydra-quality-runner", + "started_at": "2026-04-21T07:27:08Z", + "ended_at": "2026-04-21T07:27:08Z", + "exit_code": 1, + "checks_run": [ + "php -l", + "composer check:strict (phpcs)", + "composer check:strict (phpmd)", + "composer check:strict (psalm)", + "composer check:strict (phpstan)", + "phpmetrics", + "composer audit", + "spdx-headers", + "forbidden-patterns", + "npm run lint (eslint)", + "npm run lint (stylelint)", + "npm audit" + ], + "checks_skipped": [ + "publiccode", + "stub-scan", + "gitleaks", + "trivy", + "composer test:unit (phpunit)", + "newman" + ], + "gates": { + "php-lint": "pass", + "phpcs": "fail", + "phpmd": "pass", + "psalm": "pass", + "phpstan": "pass", + "phpmetrics": "pass", + "composer-audit": "pass", + "spdx-headers": "pass", + "publiccode": "skip", + "forbidden-patterns": "pass", + "eslint": "fail", + "stylelint": "fail", + "npm-audit": "pass", + "stub-scan": "skip", + "gitleaks": "skip", + "trivy": "skip", + "phpunit": "skip", + "newman": "skip" + }, + "findings": [ + { + "id": "prq-phpcs", + "severity": "WARNING", + "gate": "phpcs", + "rule": "composer check:strict (phpcs) failing", + "status": "open", + "note": "...\nThe repository at \"/server/apps/app\" does not have the correct ownership and git refuses to use it:\n\nfatal: detected dubious ownership in repository at '/server/apps/app'\nTo add an exception for this directory, call:\n\ngit config --global --add safe.directory /server/apps/app\n\nComposer could not detect the root package (conductionnl/procest) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version\n\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[31mE\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m", + "autofixable": true + }, + { + "id": "prq-eslint", + "severity": "WARNING", + "gate": "eslint", + "rule": "npm run lint (eslint) failing", + "status": "open", + "note": "...\n\n> procest@0.1.0 lint\n> eslint src\n\nsh: 1: eslint: not found", + "autofixable": true + }, + { + "id": "prq-stylelint", + "severity": "WARNING", + "gate": "stylelint", + "rule": "npm run lint (stylelint) failing", + "status": "open", + "note": "...\n\n> procest@0.1.0 stylelint\n> stylelint src/**/*.vue src/**/*.scss src/**/*.css\n\nsh: 1: stylelint: not found", + "autofixable": true + } + ], + "verdict": "fail" } ] } From b599bc664c3618f04c3de4b67b6dcb26f3193ff2 Mon Sep 17 00:00:00 2001 From: Juan Claude van Damme Date: Tue, 21 Apr 2026 07:39:27 +0000 Subject: [PATCH 08/11] fix(review): apply code review fixes - HealthController: add class-level @spec tag (ADR-003) - MetricsController: add class-level @spec tags; add @spec to index(); fix @param-before-@spec ordering in getCached() docblock (phpcs ERROR) - HealthControllerTest: named parameters for createMock/assert calls (ADR-015), fix blank-line spacing - MetricsControllerTest: named parameters for createMock/assert calls (ADR-015), fix blank-line spacing Co-fixed-by: Juan Claude van Damme --- lib/Controller/HealthController.php | 2 + lib/Controller/MetricsController.php | 12 +++- .../Unit/Controller/HealthControllerTest.php | 57 ++++++++---------- .../Unit/Controller/MetricsControllerTest.php | 60 ++++++++----------- 4 files changed, 63 insertions(+), 68 deletions(-) diff --git a/lib/Controller/HealthController.php b/lib/Controller/HealthController.php index 39befb79..b54dbf71 100644 --- a/lib/Controller/HealthController.php +++ b/lib/Controller/HealthController.php @@ -33,6 +33,8 @@ /** * Controller for health check endpoints. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-4 + * * @psalm-suppress UnusedClass */ class HealthController extends Controller diff --git a/lib/Controller/MetricsController.php b/lib/Controller/MetricsController.php index 6df66126..0f9aa60f 100644 --- a/lib/Controller/MetricsController.php +++ b/lib/Controller/MetricsController.php @@ -33,6 +33,10 @@ /** * Controller for exposing Prometheus metrics. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-1 + * @spec openspec/changes/prometheus-metrics/tasks.md#task-2 + * @spec openspec/changes/prometheus-metrics/tasks.md#task-3 + * * @psalm-suppress UnusedClass */ class MetricsController extends Controller @@ -67,6 +71,10 @@ public function __construct( /** * Return Prometheus metrics in text exposition format. * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-1 + * @spec openspec/changes/prometheus-metrics/tasks.md#task-2 + * @spec openspec/changes/prometheus-metrics/tasks.md#task-3 + * * @NoCSRFRequired * * @return TextPlainResponse Prometheus-formatted metrics @@ -199,13 +207,13 @@ private function collectMetrics(): string * * Falls back to direct computation if APCu is unavailable. * - * @spec openspec/changes/prometheus-metrics/tasks.md#task-3 - * * @param string $key The cache key * @param int $ttl Cache TTL in seconds * @param callable $compute Callable that computes the value on cache miss * * @return mixed The cached or freshly computed value + * + * @spec openspec/changes/prometheus-metrics/tasks.md#task-3 */ private function getCached(string $key, int $ttl, callable $compute): mixed { diff --git a/tests/Unit/Controller/HealthControllerTest.php b/tests/Unit/Controller/HealthControllerTest.php index 67592e58..c5de6ccf 100644 --- a/tests/Unit/Controller/HealthControllerTest.php +++ b/tests/Unit/Controller/HealthControllerTest.php @@ -74,7 +74,6 @@ class HealthControllerTest extends TestCase */ private HealthController $controller; - /** * Set up test fixtures. * @@ -82,21 +81,20 @@ class HealthControllerTest extends TestCase */ protected function setUp(): void { - $this->request = $this->createMock(IRequest::class); - $this->db = $this->createMock(IDBConnection::class); - $this->appManager = $this->createMock(IAppManager::class); - $this->logger = $this->createMock(LoggerInterface::class); + $this->request = $this->createMock(originalClassName: IRequest::class); + $this->db = $this->createMock(originalClassName: IDBConnection::class); + $this->appManager = $this->createMock(originalClassName: IAppManager::class); + $this->logger = $this->createMock(originalClassName: LoggerInterface::class); $this->controller = new HealthController( - $this->request, - $this->db, - $this->appManager, - $this->logger, + request: $this->request, + db: $this->db, + appManager: $this->appManager, + logger: $this->logger, ); }//end setUp() - /** * Test healthy system returns 200 with ok status. * @@ -104,8 +102,8 @@ protected function setUp(): void */ public function testHealthySystemReturnsOk(): void { - $qbMock = $this->createMock(\OCP\DB\QueryBuilder\IQueryBuilder::class); - $resultMock = $this->createMock(\OCP\DB\IResult::class); + $qbMock = $this->createMock(originalClassName: \OCP\DB\QueryBuilder\IQueryBuilder::class); + $resultMock = $this->createMock(originalClassName: \OCP\DB\IResult::class); $qbMock->method('select')->willReturnSelf(); $qbMock->method('createFunction')->willReturn('1'); @@ -124,15 +122,14 @@ public function testHealthySystemReturnsOk(): void $response = $this->controller->index(); $data = $response->getData(); - $this->assertSame(Http::STATUS_OK, $response->getStatus()); - $this->assertSame('ok', $data['status']); - $this->assertSame('ok', $data['checks']['database']); - $this->assertSame('ok', $data['checks']['openregister']); - $this->assertSame('ok', $data['checks']['filesystem']); + $this->assertSame(expected: Http::STATUS_OK, actual: $response->getStatus()); + $this->assertSame(expected: 'ok', actual: $data['status']); + $this->assertSame(expected: 'ok', actual: $data['checks']['database']); + $this->assertSame(expected: 'ok', actual: $data['checks']['openregister']); + $this->assertSame(expected: 'ok', actual: $data['checks']['filesystem']); }//end testHealthySystemReturnsOk() - /** * Test that OpenRegister unavailable results in error status. * @@ -140,8 +137,8 @@ public function testHealthySystemReturnsOk(): void */ public function testOpenRegisterUnavailableReturnsError(): void { - $qbMock = $this->createMock(\OCP\DB\QueryBuilder\IQueryBuilder::class); - $resultMock = $this->createMock(\OCP\DB\IResult::class); + $qbMock = $this->createMock(originalClassName: \OCP\DB\QueryBuilder\IQueryBuilder::class); + $resultMock = $this->createMock(originalClassName: \OCP\DB\IResult::class); $qbMock->method('select')->willReturnSelf(); $qbMock->method('createFunction')->willReturn('1'); @@ -160,14 +157,13 @@ public function testOpenRegisterUnavailableReturnsError(): void $response = $this->controller->index(); $data = $response->getData(); - $this->assertSame(Http::STATUS_SERVICE_UNAVAILABLE, $response->getStatus()); - $this->assertSame('error', $data['status']); - $this->assertSame('ok', $data['checks']['database']); - $this->assertSame('failed: app not enabled', $data['checks']['openregister']); + $this->assertSame(expected: Http::STATUS_SERVICE_UNAVAILABLE, actual: $response->getStatus()); + $this->assertSame(expected: 'error', actual: $data['status']); + $this->assertSame(expected: 'ok', actual: $data['checks']['database']); + $this->assertSame(expected: 'failed: app not enabled', actual: $data['checks']['openregister']); }//end testOpenRegisterUnavailableReturnsError() - /** * Test that database unreachable results in error status. * @@ -188,13 +184,12 @@ public function testDatabaseUnreachableReturnsError(): void $response = $this->controller->index(); $data = $response->getData(); - $this->assertSame(Http::STATUS_SERVICE_UNAVAILABLE, $response->getStatus()); - $this->assertSame('error', $data['status']); - $this->assertStringContainsString('failed:', $data['checks']['database']); + $this->assertSame(expected: Http::STATUS_SERVICE_UNAVAILABLE, actual: $response->getStatus()); + $this->assertSame(expected: 'error', actual: $data['status']); + $this->assertStringContainsString(needle: 'failed:', haystack: $data['checks']['database']); }//end testDatabaseUnreachableReturnsError() - /** * Test that the response includes version information. * @@ -214,9 +209,7 @@ public function testResponseIncludesVersion(): void $response = $this->controller->index(); $data = $response->getData(); - $this->assertSame('1.2.3', $data['version']); + $this->assertSame(expected: '1.2.3', actual: $data['version']); }//end testResponseIncludesVersion() - - }//end class diff --git a/tests/Unit/Controller/MetricsControllerTest.php b/tests/Unit/Controller/MetricsControllerTest.php index 60f74744..9bf1b7bb 100644 --- a/tests/Unit/Controller/MetricsControllerTest.php +++ b/tests/Unit/Controller/MetricsControllerTest.php @@ -73,7 +73,6 @@ class MetricsControllerTest extends TestCase */ private MetricsController $controller; - /** * Set up test fixtures. * @@ -81,21 +80,20 @@ class MetricsControllerTest extends TestCase */ protected function setUp(): void { - $this->request = $this->createMock(IRequest::class); - $this->db = $this->createMock(IDBConnection::class); - $this->appManager = $this->createMock(IAppManager::class); - $this->logger = $this->createMock(LoggerInterface::class); + $this->request = $this->createMock(originalClassName: IRequest::class); + $this->db = $this->createMock(originalClassName: IDBConnection::class); + $this->appManager = $this->createMock(originalClassName: IAppManager::class); + $this->logger = $this->createMock(originalClassName: LoggerInterface::class); $this->controller = new MetricsController( - $this->request, - $this->db, - $this->appManager, - $this->logger, + request: $this->request, + db: $this->db, + appManager: $this->appManager, + logger: $this->logger, ); }//end setUp() - /** * Test that the index method returns a TextPlainResponse. * @@ -112,15 +110,14 @@ public function testIndexReturnsTextPlainResponse(): void $response = $this->controller->index(); - $this->assertSame(200, $response->getStatus()); + $this->assertSame(expected: 200, actual: $response->getStatus()); $headers = $response->getHeaders(); - $this->assertArrayHasKey('Content-Type', $headers); - $this->assertSame('text/plain; version=0.0.4; charset=utf-8', $headers['Content-Type']); + $this->assertArrayHasKey(key: 'Content-Type', array: $headers); + $this->assertSame(expected: 'text/plain; version=0.0.4; charset=utf-8', actual: $headers['Content-Type']); }//end testIndexReturnsTextPlainResponse() - /** * Test that the metrics output contains the expected metric families. * @@ -138,21 +135,20 @@ public function testMetricsContainsExpectedFamilies(): void $content = $response->render(); // Verify required metric families are present. - $this->assertStringContainsString('# HELP procest_info Application information', $content); - $this->assertStringContainsString('# TYPE procest_info gauge', $content); - $this->assertStringContainsString('# HELP procest_up Whether the application is healthy', $content); - $this->assertStringContainsString('# TYPE procest_up gauge', $content); - $this->assertStringContainsString('# HELP procest_cases_total', $content); - $this->assertStringContainsString('# TYPE procest_cases_total gauge', $content); - $this->assertStringContainsString('# HELP procest_cases_overdue_total', $content); - $this->assertStringContainsString('# HELP procest_cases_created_today', $content); - $this->assertStringContainsString('# TYPE procest_cases_created_today gauge', $content); - $this->assertStringContainsString('# HELP procest_tasks_total', $content); - $this->assertStringContainsString('# HELP procest_tasks_overdue_total', $content); + $this->assertStringContainsString(needle: '# HELP procest_info Application information', haystack: $content); + $this->assertStringContainsString(needle: '# TYPE procest_info gauge', haystack: $content); + $this->assertStringContainsString(needle: '# HELP procest_up Whether the application is healthy', haystack: $content); + $this->assertStringContainsString(needle: '# TYPE procest_up gauge', haystack: $content); + $this->assertStringContainsString(needle: '# HELP procest_cases_total', haystack: $content); + $this->assertStringContainsString(needle: '# TYPE procest_cases_total gauge', haystack: $content); + $this->assertStringContainsString(needle: '# HELP procest_cases_overdue_total', haystack: $content); + $this->assertStringContainsString(needle: '# HELP procest_cases_created_today', haystack: $content); + $this->assertStringContainsString(needle: '# TYPE procest_cases_created_today gauge', haystack: $content); + $this->assertStringContainsString(needle: '# HELP procest_tasks_total', haystack: $content); + $this->assertStringContainsString(needle: '# HELP procest_tasks_overdue_total', haystack: $content); }//end testMetricsContainsExpectedFamilies() - /** * Test that the info gauge includes the nextcloud_version label. * @@ -171,13 +167,12 @@ public function testInfoGaugeIncludesNextcloudVersion(): void // The info line should contain nextcloud_version label. $this->assertMatchesRegularExpression( - '/procest_info\{.*nextcloud_version="[^"]*".*\} 1/', - $content + pattern: '/procest_info\{.*nextcloud_version="[^"]*".*\} 1/', + string: $content ); }//end testInfoGaugeIncludesNextcloudVersion() - /** * Test that procest_up is 0 when database is unreachable. * @@ -194,11 +189,10 @@ public function testUpGaugeReflectsDatabaseHealth(): void $response = $this->controller->index(); $content = $response->render(); - $this->assertStringContainsString('procest_up 0', $content); + $this->assertStringContainsString(needle: 'procest_up 0', haystack: $content); }//end testUpGaugeReflectsDatabaseHealth() - /** * Test that the cases_created_today metric has a valid format. * @@ -216,9 +210,7 @@ public function testCasesCreatedTodayMetricFormat(): void $content = $response->render(); // Should have a valid integer value (0 since DB is mocked to fail). - $this->assertStringContainsString('procest_cases_created_today 0', $content); + $this->assertStringContainsString(needle: 'procest_cases_created_today 0', haystack: $content); }//end testCasesCreatedTodayMetricFormat() - - }//end class From 568da8c4dd8cac559ec82adcce7d23680fbb268e Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 09:53:58 +0200 Subject: [PATCH 09/11] chore(hydra): record quality-recheck stage [skip ci] --- .../changes/prometheus-metrics/hydra.json | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index 4d7009a0..8f198aff 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -435,6 +435,86 @@ } ], "verdict": "fail" + }, + { + "stage": "quality-recheck", + "persona": "orchestrator", + "container": "hydra-quality-runner", + "started_at": "2026-04-21T07:53:58Z", + "ended_at": "2026-04-21T07:53:58Z", + "exit_code": 1, + "checks_run": [ + "php -l", + "composer check:strict (phpcs)", + "composer check:strict (phpmd)", + "composer check:strict (psalm)", + "composer check:strict (phpstan)", + "phpmetrics", + "composer audit", + "spdx-headers", + "forbidden-patterns", + "npm run lint (eslint)", + "npm run lint (stylelint)", + "npm audit" + ], + "checks_skipped": [ + "publiccode", + "stub-scan", + "gitleaks", + "trivy", + "composer test:unit (phpunit)", + "newman" + ], + "gates": { + "php-lint": "pass", + "phpcs": "fail", + "phpmd": "pass", + "psalm": "pass", + "phpstan": "pass", + "phpmetrics": "pass", + "composer-audit": "pass", + "spdx-headers": "pass", + "publiccode": "skip", + "forbidden-patterns": "pass", + "eslint": "fail", + "stylelint": "fail", + "npm-audit": "pass", + "stub-scan": "skip", + "gitleaks": "skip", + "trivy": "skip", + "phpunit": "skip", + "newman": "skip" + }, + "findings": [ + { + "id": "qrc-phpcs", + "severity": "WARNING", + "gate": "phpcs", + "rule": "composer check:strict (phpcs) failing", + "status": "open", + "note": "...\nThe repository at \"/server/apps/repo\" does not have the correct ownership and git refuses to use it:\n\nfatal: detected dubious ownership in repository at '/server/apps/repo'\nTo add an exception for this directory, call:\n\ngit config --global --add safe.directory /server/apps/repo\n\nComposer could not detect the root package (conductionnl/procest) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version\n\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m.\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m.\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33mW\u001b[0m\u001b[33m", + "autofixable": true + }, + { + "id": "qrc-eslint", + "severity": "WARNING", + "gate": "eslint", + "rule": "npm run lint (eslint) failing", + "status": "open", + "note": "...\n\n> procest@0.1.0 lint\n> eslint src\n\nsh: 1: eslint: not found", + "autofixable": true + }, + { + "id": "qrc-stylelint", + "severity": "WARNING", + "gate": "stylelint", + "rule": "npm run lint (stylelint) failing", + "status": "open", + "note": "...\n\n> procest@0.1.0 stylelint\n> stylelint src/**/*.vue src/**/*.scss src/**/*.css\n\nsh: 1: stylelint: not found", + "autofixable": true + } + ], + "verdict": "fail" } ] } From 42c842e1653f936d029363d1eabf08c15f741187 Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 09:54:03 +0200 Subject: [PATCH 10/11] chore(hydra): pattern-tag reviewer-ran-applied-no-fixes [skip ci] --- openspec/changes/prometheus-metrics/hydra.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index 8f198aff..b3f2e7f0 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -337,7 +337,8 @@ "outcome": "in-flight", "outcome_reason": null, "pattern_tags": [ - "browser-test-nc-setup-failed" + "browser-test-nc-setup-failed", + "reviewer-ran-applied-no-fixes" ], "stages": [ { From 09aef01562600455221efa64d29aca30c5aa657e Mon Sep 17 00:00:00 2001 From: Hydra Pipeline Date: Tue, 21 Apr 2026 09:54:08 +0200 Subject: [PATCH 11/11] chore(hydra): close cycle outcome=needs-input [skip ci] --- openspec/changes/prometheus-metrics/hydra.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openspec/changes/prometheus-metrics/hydra.json b/openspec/changes/prometheus-metrics/hydra.json index b3f2e7f0..fd86d6e6 100644 --- a/openspec/changes/prometheus-metrics/hydra.json +++ b/openspec/changes/prometheus-metrics/hydra.json @@ -333,9 +333,9 @@ "cycle": 3, "trigger": "build:queued", "started_at": "2026-04-21T04:44:27Z", - "ended_at": null, - "outcome": "in-flight", - "outcome_reason": null, + "ended_at": "2026-04-21T07:54:07Z", + "outcome": "needs-input", + "outcome_reason": "deterministic checks still failing \u2014 reviewers ran but applied no fixes, builder output remains broken", "pattern_tags": [ "browser-test-nc-setup-failed", "reviewer-ran-applied-no-fixes"