Skip to content

feat(adr-compliance): close gaps against 17 org ADRs#20

Open
rubenvdlinde wants to merge 8 commits into
fix/header-consistency-info-emailfrom
fix/adr-examples
Open

feat(adr-compliance): close gaps against 17 org ADRs#20
rubenvdlinde wants to merge 8 commits into
fix/header-consistency-info-emailfrom
fix/adr-examples

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #19. Audits the template against every MUST/SHALL/REQUIRED rule in the 17 org-wide ADRs (hydra/openspec/architecture/adr-*.md) and adds minimal concrete examples for every demonstrable gap, so builders who copy from this template get the right patterns by default.

Full audit table: docs/adr-audit.md — 56 rules checked, 45 already demonstrated, 13 added/fixed, 5 partial with documented exceptions, 6 N/A.

What's added/fixed

Seed data (ADR-001): 3 realistic @self-enveloped objects in lib/Settings/app_template_register.json so installs are not empty.

Observability (ADR-006): new HealthController (public JSON, verifies OpenRegister) + MetricsController (admin Prometheus text with {app}_health_status + {app}_info). Routes declared them already — controllers were missing so /api/metrics and /api/health returned 500.

API + security (ADR-002 / ADR-005):

  • CORS OPTIONS route for public /api/health.
  • SettingsController now try/catches every action, returns static Operation failed message, logs real error server-side.
  • SettingsService::loadConfiguration no longer leaks $e->getMessage() to clients.

Frontend (ADR-004 / ADR-015):

  • Settings store: raw fetch()@nextcloud/axios (CSRF).
  • App.vue, MainMenu, Settings, UserSettings, ItemDetail no longer import directly from @nextcloud/vue — all go through @conduction/nextcloud-vue.
  • ItemDetail replaces window.confirm() with NcDialog.
  • Deep link URL template fixed from hash (#/examples/{uuid}) to path format (/examples/{uuid}) per Vue router history mode.
  • Every await store.action() now wrapped in try/catch with user-facing feedback.
  • Bare t(…)this.t(…) in Settings.vue.

Spec traceability (ADR-003): every class and public method in lib/ now has @spec openspec/changes/example-change/tasks.md#task-N (was only on Application.php). Explicitly illustrative — copies get replaced with real paths.

Testing (ADR-008): added error-path unit test testIndexReturnsGenericErrorOnServiceException — asserts 500 + generic message + no leak of exception text.

Base branch

Stacked on top of fix/header-consistency-info-email (PR #19) so both sets of fixes land coherently. Retarget base to development once #19 merges, or merge #19 first and this will fast-forward.

Test plan

  • php -l clean on all 10 modified/added PHP files
  • Register JSON valid
  • composer check:strict clean locally
  • Scaffold a new app from this template and verify curl /api/health returns JSON, curl /api/metrics returns Prometheus text, and the 3 seed objects appear in the list view
  • ESLint / stylelint clean on updated Vue / JS

…DR mandate

Audits the template against the 17 org-wide ADRs
(hydra/openspec/architecture/adr-*.md) and closes every demonstrable gap so
builders who copy from this template get the right patterns by default.

Full audit table: docs/adr-audit.md.

Key additions / fixes:

- ADR-001 seed data: add 3 realistic @self-enveloped objects to
  app_template_register.json so installs are not empty.
- ADR-002 CORS: register OPTIONS route for public /api/health.
- ADR-003 @SPEC tags: add file-level + method-level @SPEC tags to every
  class in lib/ and to the test class (was only on Application.php).
- ADR-004 frontend:
  - switch settings store from raw fetch() to @nextcloud/axios (CSRF).
  - route all @nextcloud/vue imports through @conduction/nextcloud-vue
    (App.vue, MainMenu, Settings, UserSettings, ItemDetail).
  - replace window.confirm() in ItemDetail with NcDialog.
  - fix deep link URL template from hash (#/examples/{uuid}) to path
    format (/examples/{uuid}) per history-mode requirement.
  - wrap every await store.action() in try/catch with user feedback.
- ADR-005 security: SettingsController now try/catches every action and
  returns a static "Operation failed" message on exception; logs the real
  error server-side. SettingsService::loadConfiguration no longer leaks
  $e->getMessage() to clients.
- ADR-006 observability: add HealthController (public JSON, verifies
  OpenRegister connectivity) and MetricsController (admin-only Prometheus
  text, includes {app}_health_status + {app}_info). Routes already
  declared them; controllers were missing (500 on call).
- ADR-008 testing: add error-path unit test for SettingsController.

All PHP files pass php -l. Follow-ups (documented in audit): @Licence vs
@license sweep, private readonly DI params, SettingsServiceTest.
…ompliance

PHP files carry licence + copyright via PHPDoc @tags in the main
docblock (ADR-014). Duplicating that as SPDX-FileCopyrightText /
SPDX-License-Identifier inside every docblock was redundant — the
@copyright / @license / @author tags already say the same thing.

- Remove the SPDX-FileCopyrightText + SPDX-License-Identifier lines
  from every PHP file header. @copyright / @license stay.
- Add REUSE.toml at repo root so `reuse lint` still passes — it
  covers **/*.php, non-PHP sources, and configs/data in a handful of
  rules. New apps scaffolded from this template inherit the same
  pattern.

This aligns the template with the hydra spdx-headers gate rewrite
(ConductionNL/hydra#66) which now enforces @license + @copyright
PHPDoc tags, and the ADR-014 update that mandates @tag-only on PHP.
Renames the template's demo schema from 'example' to 'article' and aligns
its properties to schema.org/Article (name, description, identifier,
dateCreated, author) per ADR-011. Updates the deep-link listener to point
at the new 'article' schema slug and renames seed objects to match.

The template is the canonical reference apps copy from, so it must
demonstrate the ADR-011 rule (use schema.org vocabulary instead of
ad-hoc domain types whenever a standard type fits).
Adds PHPUnit coverage for SettingsService, mirroring the shape of the
existing SettingsControllerTest. All 6 dependencies (IAppConfig,
IAppManager, ContainerInterface, IGroupManager, IUserSession,
LoggerInterface) are mocked and every method is exercised:

- isOpenRegisterAvailable() — both installed and not-installed branches
- getSettings() — isAdmin=true (admin user), isAdmin=false (non-admin),
  and the no-user/guest branch that must skip groupManager->isAdmin()
- updateSettings() — persists known CONFIG_KEYS via IAppConfig, ignores
  unknown keys
- loadConfiguration() — success path (force=true), OpenRegister-missing
  short-circuit, and the Throwable-caught branch per ADR-005 (logs the
  real exception server-side, returns a static generic message)

10 test methods / 32 assertions — well above the ADR-008 floor of >= 3
methods per logical unit.

Also extends tests/bootstrap-unit.php to autoload NC core classes
(OCP\IAppConfig, OCP\IRequest, etc.) without triggering OC::init().
This allows the existing SettingsControllerTest to run too (previously
it failed with "Class OCP\\IRequest does not exist") so both test
classes now execute: 15 tests, 49 assertions, all passing.
Demonstrates the ADR-005 mandate that #[NoAdminRequired] mutation
endpoints MUST carry an explicit backend auth check. The template had
no domain mutation endpoint to demo this on, so the audit listed it as
'partial' — this commit adds a minimal, copy-pasteable example.

- lib/Service/ItemService::delete($id, $userId) enforces an admin-OR-owner
  check via IGroupManager::isAdmin() and the OpenRegister @self.owner
  metadata, returning a STATUS_* enum the controller maps to HTTP codes.
- lib/Controller/ItemController::destroy($id) declares #[NoAdminRequired],
  derives the acting UID from IUserSession (never a request param), and
  maps STATUS_DELETED→204, STATUS_FORBIDDEN→403, STATUS_NOT_FOUND→404,
  STATUS_UNAVAILABLE→503, Throwable→generic 500 with server-side log.
- appinfo/routes.php registers DELETE /api/items/{id} before the SPA
  catch-all, per existing route-ordering comments.
- Follows OWASP A01: returns 404 (not 403) for non-existent IDs so
  response codes don't leak existence.
- ItemServiceTest (6 methods) and ItemControllerTest (5 methods) lock
  down all auth branches + the HTTP mapping. Full unit suite now runs
  26 tests / 78 assertions, all passing.
Audits the whole repo for PHPDoc @Licence (UK) and confirms every PHP
file already uses @license (US) — which is the PHPCS standard. The
stale ADR-014 audit entry wrongly flagged this as a partial finding
and as a follow-up; this commit resolves both.

Also rolls up the audit with the three other ADR follow-ups now
resolved this branch:

- ADR-005: DELETE /api/items/{id} + ItemService auth → ✅
- ADR-008: SettingsServiceTest + ItemServiceTest + ItemControllerTest → ✅
- ADR-011: schema.org/Article alignment → ✅
- ADR-002 pagination: reclassified as N/A (OpenRegister facilitates
  this automatically via useListView — apps don't need list endpoints)

`<licence>agpl</licence>` in appinfo/info.xml is intentionally kept —
it's a Nextcloud-schema XML element, not PHPDoc.
Fixes lint violations surfaced while running composer phpcs + phpmd on
this branch:

- lib/Service/ItemService.php — inline IFs in extractOwner() rewritten
  as explicit if-blocks, extractOwner() call updated to use named
  parameter (ADR custom sniff: internal calls must use named params),
  docblock alignment tweak.
- lib/Controller/HealthController.php — pre-existing inline IFs in
  index() replaced with a guarded mutation pattern (sets degraded
  default, overrides on healthy) to also clear the PHPMD ElseExpression.
- lib/Controller/MetricsController.php — pre-existing equals-sign
  alignment between $prefix and $healthy.

`composer phpcs` + `composer phpmd` (minus a pre-existing BooleanArgumentFlag
warning on SettingsService::loadConfiguration($force) which would require
an API change) + `composer psalm` + `composer phpstan` are now clean.
`composer test:unit` still reports 26 / 26 passing.
@rubenvdlinde
Copy link
Copy Markdown
Contributor Author

ADR follow-ups — finished (4 commits + 1 quality sweep)

Picked up the prior agent's in-progress schema.org work, finished it, and knocked out the three other follow-ups from docs/adr-audit.md. Pagination is dropped from scope per your note (OpenRegister facilitates it — apps don't need list endpoints).

Commits on this branch (on top of 3583cfe)

SHA Subject Notes
95433f9 feat(adr-011): align example schema to schema.org/Article Finished the prior agent's work: schema renamed examplearticle, properties aligned to schema.org/Article (name, description, identifier, dateCreated, author) with x-schema-org annotation. Deep-link listener + seed objects updated.
e185a8e feat(adr-008): add SettingsServiceTest with all branches 10 test methods / 32 assertions covering isOpenRegisterAvailable() (installed/missing), getSettings() (admin/non-admin/no-user), updateSettings() (persist/ignore-unknown), loadConfiguration() (success/missing/Throwable-caught per ADR-005). Also taught tests/bootstrap-unit.php to autoload NC core classes without OC::init() so the existing SettingsControllerTest starts passing too.
0ba8184 feat(adr-005): per-object auth demo on DELETE /api/items/{id} New ItemController::destroy() (#[NoAdminRequired]) + ItemService::delete() with admin-OR-owner check via IGroupManager::isAdmin() and the OpenRegister @self.owner metadata. Returns 204/403/404/503 with generic messages per ADR-005. Route registered before SPA catch-all. ItemServiceTest (6 methods) + ItemControllerTest (5 methods) lock down every auth branch.
34c57a6 fix(adr-014): @Licence@license PHPDoc spelling Audited repo — no PHP files actually use @licence; all use @license. Rolled up docs/adr-audit.md to mark ADR-005/008/011/014 resolved and reclassified pagination as N/A.
7ee06aa chore(quality): fix PHPCS inline IFs, named-param, alignment Pre-existing lint violations on HealthController + MetricsController plus violations on the new ItemService, per the team rule of fixing pre-existing quality issues when encountered.

Dropped from scope

  • Pagination demo (ADR-002) — OpenRegister facilitates _page / _limit / total / pages automatically through createObjectStore + useListView. The template doesn't need its own list endpoint. docs/adr-audit.md row reclassified N/A.

Verify

  • php -l clean on every touched PHP file
  • composer phpcs 12/12 files clean
  • composer psalm — no errors
  • composer phpstan — no errors
  • composer phpmd — clean (one pre-existing BooleanArgumentFlag warning on SettingsService::loadConfiguration(\$force) left alone; would require an API break to fix)
  • composer test:unit (via the Nextcloud container) — 26 tests, 78 assertions, all passing (1 placeholder + 4 SettingsController + 10 SettingsService + 5 ItemController + 6 ItemService)

Audit roll-up

docs/adr-audit.md summary updated: 49 demonstrated, 17 added/fixed this branch, 2 partial, 7 N/A.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant