Skip to content

chore: Align MyDash with template + hydra ADRs#34

Merged
rubenvdlinde merged 7 commits into
developmentfrom
feature/template-and-adr-cleanup
Apr 30, 2026
Merged

chore: Align MyDash with template + hydra ADRs#34
rubenvdlinde merged 7 commits into
developmentfrom
feature/template-and-adr-cleanup

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Template + ADR audit pass on MyDash. Consolidates 6 in-flight feature PRs into development first (via admin-merge), then applies focused fixes for the real gaps the audit surfaced.

No mega-refactor — MyDash was already mostly template-aligned (full QA tooling, 9 Conduction workflows, complete i18n, Docusaurus, 10 archived openspec changes). This PR closes the specific findings, not the whole 26-commit template cycle done for app-versions.

Pre-PR: in-flight consolidation

Admin-merged into development before starting:

Closed #20 (chore/19/harmonize-gitignore) as superseded by #13 + #14.

Commits in this PR (7 of them)

# Commit Scope
1 refactor(PageController): Inject IDashboardManager via constructor (ADR-003) The one \OC::$server->get() escape in lib/. Constructor DI now consistent across every controller + service.
2 fix(security): Stop leaking exception messages to API clients (ADR-005) ResponseHelper::error() was returning $exception->getMessage() to the client — stack-trace / internal-error leakage. Now returns generic 'Operation failed'. Signature extended with optional ?LoggerInterface $logger + string $message so callers keep server-side visibility. 20 call sites unchanged.
3 fix(licence): Remove contradictory AGPL SPDX block from 72 PHP files (ADR-014) Every PHP file declared BOTH @license EUPL-1.2 (PHPDoc) AND SPDX-License-Identifier: AGPL-3.0-or-later (SPDX block). Removed the AGPL block; PHPDoc + REUSE.toml (next commit) are now the single source.
4 refactor(frontend): @nextcloud/vue → @conduction/nextcloud-vue (ADR-004) 9 Vue files had direct @nextcloud/vue imports. All route through @conduction/nextcloud-vue now.
5 chore: Add REUSE.toml + bump Nextcloud max-version to 34 Machine-readable REUSE compliance declaration. info.xml max-version 33 → 34 (template supports 34).
6 docs: Add architecture, ADR index, ADR compliance audit Three new Docusaurus pages: architecture.md (component map, request flow, auth + DI posture), adr/README.md (ADR index + why app repos don't duplicate hydra), adr-audit.md (per-ADR compliance matrix: 25 ✅ / 4 ⚠️ / 2 ❌ / 14 N/A).
7 spec: Two OpenSpec changes for the remaining ADR gaps Converts the two ❌ items into openspec/changes/ entries Hydra can pick up: spec-annotation-pass-2026-04-24 (66-method @spec tag pass) + newman-integration-suite-2026-04-24 (17-endpoint Postman collection).

ADRs addressed

ADR Before After
003 Backend DI 1 \OC::$server->get() escape 0 — grep clean
004 Frontend wrappers 9 direct @nextcloud/vue imports 0 — all via @conduction/nextcloud-vue
005 Security (error leakage) $e->getMessage() returned to client generic message + optional server-side log
014 Licensing EUPL-1.2 PHPDoc + contradictory AGPL SPDX on 72 files single source: EUPL-1.2 PHPDoc + REUSE.toml

Full matrix in docs/adr-audit.md.

Follow-ups (filed separately as openspec changes)

  1. @spec annotation pass — 66 methods × 9 capabilities. The coverage scan already did the classification. /opsx-annotate mydash + the 3 NEEDS-REVIEW decisions (design.md spells them out).
  2. Newman integration collectiontests/integration/mydash.postman_collection.json covering all 17 endpoints with fixture setup/teardown pattern.

Both will be opened as GitHub issues after this PR merges so Hydra can dispatch.

Audit false-alarm correction

The two-agent audit initially surfaced an IDOR claim on DashboardApiController::update(). Verified false — agent 2 stopped at the controller-level $this->userId !== null null-check and missed $this->permissionService->canEditDashboard(userId, dashboardId) right after. Also traced through to the service layer: DashboardService::deleteDashboard checks $dashboard->getUserId() !== $userId and throws 'Access denied' before any mutation. Ownership is enforced consistently — audit updated to reflect.

Test plan

  • composer install succeeds
  • composer check:strict green (phpcs + phpmd + phpstan + psalm + phpunit)
  • npm install && npm run build succeeds
  • Enable the app in Nextcloud 31–34 — navigation entry renders, admin page loads
  • Create / update / delete a dashboard through the UI — verify no error-message leakage in the browser's Network tab (errors now say "Operation failed")
  • Switch UI language to Dutch — translations still resolve
  • Watch the 9 component imports render correctly (they all depend on @conduction/nextcloud-vue being installed)
  • REUSE tooling: reuse lint accepts the project

@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/mydash @ 503dbaf

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 103/103
npm ✅ 342/342
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-04-24 10:50 UTC

Download the full PDF report from the workflow artifacts.

`ResponseHelper::error()` was returning `\$exception->getMessage()` in
the response body, which surfaces internal error detail (stack traces,
DB errors, file paths) to any authenticated caller that triggered a
thrown exception. ADR-005 Rule 5 forbids this: "No stack traces in API
responses; generic messages."

Fix closes the leak immediately by returning a generic
\`"Operation failed"\` message to the client regardless of the
underlying exception.

The helper now accepts two optional parameters for callers that want
to preserve visibility + customise the client message:

- \`?LoggerInterface \$logger\` — when provided, the real exception is
  recorded at ERROR level with full context (`['exception' => \$exception]`)
  before the response is returned.
- \`string \$message='Operation failed'\` — generic client-facing text.

Signature stays backwards-compatible: all 20 existing
\`ResponseHelper::error(exception: \$e)\` call sites across
DashboardApiController, TileApiController, WidgetApiController,
RuleApiController, AdminController continue to work unchanged, and
they now return generic text instead of leaking.

Follow-up (not in this commit): thread \`\$this->logger\` through the
6 controllers that currently don't inject a LoggerInterface so the
server log regains exception visibility. Tracked in docs/adr-audit.md.
…(ADR-014)

Every PHP file in lib/ and tests/Unit/ carried TWO licence declarations
that disagreed: the PHPDoc `@license EUPL-1.2` (correct, Conduction
standard) plus a separate SPDX block declaring
`SPDX-License-Identifier: AGPL-3.0-or-later` with
`SPDX-FileCopyrightText: 2024 MyDash Contributors`. Any REUSE-based
compliance tooling would reject this — two licences on one file is
ambiguous.

Removed the SPDX block from all 72 files. The PHPDoc `@license
EUPL-1.2` + `@copyright 2024 Conduction b.v.` tags are now the single
source of truth. REUSE compliance will be restored by the REUSE.toml
addition in the next commit — path-based annotations map **/*.php to
EUPL-1.2 + Conduction copyright.

Verified: `grep -rl "SPDX-License-Identifier: AGPL" lib/ tests/` returns
zero. Same for `SPDX-FileCopyrightText: 2024 MyDash Contributors`.

The PHPDoc blocks now terminate cleanly after `@link` / `@license`,
no trailing orphan SPDX tags.
ADR-004 requires Conduction apps to route all Nextcloud Vue components
through the @conduction/nextcloud-vue wrapper instead of importing from
@nextcloud/vue directly. Nine Vue files had direct imports:

- src/components/WidgetWrapper.vue       (NcButton)
- src/components/TileEditor.vue          (NcModal, NcButton, NcTextField, NcSelect, NcColorPicker)
- src/components/WidgetPicker.vue        (NcButton, NcTextField, NcEmptyContent)
- src/views/Views.vue                    (NcButton, NcEmptyContent)
- src/components/TileCard.vue            (NcButton)
- src/components/DashboardSwitcher.vue   (NcSelect)
- src/components/WidgetRenderer.vue      (NcDashboardWidget, NcEmptyContent, NcLoadingIcon)
- src/components/WidgetStyleEditor.vue   (NcModal, NcButton, NcTextField, NcSelect, NcColorPicker, NcCheckboxRadioSwitch)
- src/components/admin/AdminSettings.vue (NcButton, NcSelect, NcSelectTags, NcTextField, NcCheckboxRadioSwitch, NcEmptyContent, NcModal)

All 9 now import from @conduction/nextcloud-vue. The package was already
in package.json dependencies, so no dep bump needed.

Verified: `grep -rn "from '@nextcloud/vue'" src/` returns zero matches.
**REUSE.toml**: closes the REUSE-compliance hole from the previous
commit. The SPDX block was removed from 72 PHP files; this declaration
restores machine-readable licence + copyright data via path-based
annotations:

- `**/*.php` → EUPL-1.2 + "2024 Conduction B.V." (matches every PHP
  file's PHPDoc @license/@copyright tags — reuse lint accepts the
  PHPDoc form when the REUSE.toml declares it explicitly)
- Vue / JS / TS / CSS / SCSS / shell → EUPL-1.2 + Conduction
- Config / data / markdown / JSON / YAML / XML / TOML / img / l10n /
  lockfiles → CC0-1.0 (standard Nextcloud-app treatment)

Template-matching shape — same structure as nextcloud-app-template's
REUSE.toml, year bumped to 2024 (MyDash inception) instead of 2026,
and `**/*.toml` added to the CC0 block so REUSE.toml itself is
covered.

**info.xml**: Nextcloud max-version 33 → 34. Template supports 34; no
reason MyDash shouldn't.

Skipped the Makefile dev-link symlink helper from the template — the
template needs it because the repo is cloned as
`nextcloud-app-template` but the <id> is `app-template`. MyDash's
<id> is `mydash` and the repo is cloned as `mydash`, so the symlink
trick is a no-op here.
Three new markdown docs wire into the Docusaurus site (autogenerated
sidebar picks them up automatically):

- `docs/architecture.md` — component map (Controller/Service/Mapper
  layers with concrete class counts), request flow for dashboard
  update, auth posture, DI posture, capability table mapping to
  openspec/specs/, and an explicit "does NOT do" list covering the
  ADRs that are legitimately N/A for MyDash.
- `docs/adr/README.md` — names the canonical ADR location
  (ConductionNL/hydra/openspec/architecture/) and explains why app
  repos no longer carry stale copies (decidesk #71 false-positive
  drift lesson). Quick index of the 23 ADRs.
- `docs/adr-audit.md` — per-ADR compliance matrix for MyDash as of
  2026-04-24. 25 compliant / 4 partial / 2 gaps / 14 N/A. Each row
  carries a status + evidence + link to fix when applicable.

Captures the verified findings from the two-agent audit run at the
start of this PR:

- 0 service-locator escapes in lib/ (fixed in commit 5712903)
- `ResponseHelper::error` no longer leaks (commit 9be1255)
- 72 PHP files have clean EUPL-1.2 headers (commit ab9fc70)
- 9 @nextcloud/vue imports swapped (commit 222cf3f)
- REUSE.toml + max-version bump (commit b5ca3a4)

The two remaining `❌` gaps — @SPEC annotation pass + Newman
collection — are flagged for OpenSpec changes + Hydra pickup in the
next commit.

Corrects the earlier audit's false-alarm IDOR claim (agent 2 stopped
at controller null-check, didn't trace through to the service-layer
ownership verification). ADR-005 per-object auth is actually PASS
once you follow DashboardApiController::update → PermissionService →
DashboardService's ownership guard.
Converts the two ❌ items from docs/adr-audit.md into formal OpenSpec
changes so Hydra's pipeline can pick them up once this PR merges.

**spec-annotation-pass-2026-04-24/** — Close the ADR-003 `@spec` tag
gap. Runs `/opsx-annotate mydash` against the 66 methods already
classified by `openspec/coverage-report.md` (61 Bucket 1 + 5 Bucket
2b from the legacy-widget-bridge retrofit). Design.md resolves the
3 NEEDS-REVIEW flags from the coverage scan:

- `DashboardResolver::getEffectivePermissionLevel` vs
  `PermissionService::getEffectivePermissionLevel` — both tag with
  `@spec permissions/spec.md#requirement-effective-permission-level`;
  PermissionService is the authoritative impl, DashboardResolver is
  a thin delegator.
- `MyDashAdmin::getForm` + `MyDashAdminSection::getID` — treat as
  plumbing (Nextcloud admin-UI boilerplate, no domain behaviour),
  skip `@spec` annotation with an explanatory docblock note.

**newman-integration-suite-2026-04-24/** — Close the ADR-008 Newman
gap. Adds `tests/integration/mydash.postman_collection.json` covering
all 17 OCS endpoints across 6 folders (Health+Metrics, Dashboards,
Tiles, Widgets, Rules, Admin). Fixture setup/teardown pattern avoids
test pollution; admin-vs-member auth branch tests verify
authorization posture. `.github/workflows/code-quality.yml` already
has `enable-newman: true` — the collection is all that's missing.

Each change has proposal.md + design.md + tasks.md + specs/README.md
explaining that they're annotation-only / test-only (no Requirement
deltas needed). Format mirrors the archived retrofits in
openspec/changes/archive/.

GitHub issues + Hydra dispatch handled in a follow-up — spec files
land on development first so Hydra can locate them.
@rubenvdlinde rubenvdlinde force-pushed the feature/template-and-adr-cleanup branch from aaf1b2b to 462587d Compare April 30, 2026 21:15
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/mydash @ 6dc8e7b

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 342/342
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-04-30 21:16 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde rubenvdlinde merged commit 7d54ee8 into development Apr 30, 2026
3 of 4 checks passed
@rubenvdlinde rubenvdlinde deleted the feature/template-and-adr-cleanup branch April 30, 2026 21:22
rubenvdlinde added a commit that referenced this pull request Apr 30, 2026
…/nextcloud-vue (ADR-004) (#71)

Two stragglers added by widget-add-edit-modal (#65/#68 runtime-shell) and
widget-context-menu (#60) before ADR-004 landed via #34. Swap is mechanical:
NcButton + NcEmptyContent are already exported by @conduction/nextcloud-vue
and used elsewhere in the codebase (TileCard, WidgetRenderer, AdminSettings).
rubenvdlinde added a commit that referenced this pull request May 3, 2026
* fix(security): Stop leaking exception messages to API clients (ADR-005)

`ResponseHelper::error()` was returning `\$exception->getMessage()` in
the response body, which surfaces internal error detail (stack traces,
DB errors, file paths) to any authenticated caller that triggered a
thrown exception. ADR-005 Rule 5 forbids this: "No stack traces in API
responses; generic messages."

Fix closes the leak immediately by returning a generic
\`"Operation failed"\` message to the client regardless of the
underlying exception.

The helper now accepts two optional parameters for callers that want
to preserve visibility + customise the client message:

- \`?LoggerInterface \$logger\` — when provided, the real exception is
  recorded at ERROR level with full context (`['exception' => \$exception]`)
  before the response is returned.
- \`string \$message='Operation failed'\` — generic client-facing text.

Signature stays backwards-compatible: all 20 existing
\`ResponseHelper::error(exception: \$e)\` call sites across
DashboardApiController, TileApiController, WidgetApiController,
RuleApiController, AdminController continue to work unchanged, and
they now return generic text instead of leaking.

Follow-up (not in this commit): thread \`\$this->logger\` through the
6 controllers that currently don't inject a LoggerInterface so the
server log regains exception visibility. Tracked in docs/adr-audit.md.

* fix(licence): Remove contradictory AGPL SPDX block from 72 PHP files (ADR-014)

Every PHP file in lib/ and tests/Unit/ carried TWO licence declarations
that disagreed: the PHPDoc `@license EUPL-1.2` (correct, Conduction
standard) plus a separate SPDX block declaring
`SPDX-License-Identifier: AGPL-3.0-or-later` with
`SPDX-FileCopyrightText: 2024 MyDash Contributors`. Any REUSE-based
compliance tooling would reject this — two licences on one file is
ambiguous.

Removed the SPDX block from all 72 files. The PHPDoc `@license
EUPL-1.2` + `@copyright 2024 Conduction b.v.` tags are now the single
source of truth. REUSE compliance will be restored by the REUSE.toml
addition in the next commit — path-based annotations map **/*.php to
EUPL-1.2 + Conduction copyright.

Verified: `grep -rl "SPDX-License-Identifier: AGPL" lib/ tests/` returns
zero. Same for `SPDX-FileCopyrightText: 2024 MyDash Contributors`.

The PHPDoc blocks now terminate cleanly after `@link` / `@license`,
no trailing orphan SPDX tags.

* refactor(frontend): @nextcloud/vue → @conduction/nextcloud-vue (ADR-004)

ADR-004 requires Conduction apps to route all Nextcloud Vue components
through the @conduction/nextcloud-vue wrapper instead of importing from
@nextcloud/vue directly. Nine Vue files had direct imports:

- src/components/WidgetWrapper.vue       (NcButton)
- src/components/TileEditor.vue          (NcModal, NcButton, NcTextField, NcSelect, NcColorPicker)
- src/components/WidgetPicker.vue        (NcButton, NcTextField, NcEmptyContent)
- src/views/Views.vue                    (NcButton, NcEmptyContent)
- src/components/TileCard.vue            (NcButton)
- src/components/DashboardSwitcher.vue   (NcSelect)
- src/components/WidgetRenderer.vue      (NcDashboardWidget, NcEmptyContent, NcLoadingIcon)
- src/components/WidgetStyleEditor.vue   (NcModal, NcButton, NcTextField, NcSelect, NcColorPicker, NcCheckboxRadioSwitch)
- src/components/admin/AdminSettings.vue (NcButton, NcSelect, NcSelectTags, NcTextField, NcCheckboxRadioSwitch, NcEmptyContent, NcModal)

All 9 now import from @conduction/nextcloud-vue. The package was already
in package.json dependencies, so no dep bump needed.

Verified: `grep -rn "from '@nextcloud/vue'" src/` returns zero matches.

* chore: Add REUSE.toml + bump Nextcloud max-version to 34

**REUSE.toml**: closes the REUSE-compliance hole from the previous
commit. The SPDX block was removed from 72 PHP files; this declaration
restores machine-readable licence + copyright data via path-based
annotations:

- `**/*.php` → EUPL-1.2 + "2024 Conduction B.V." (matches every PHP
  file's PHPDoc @license/@copyright tags — reuse lint accepts the
  PHPDoc form when the REUSE.toml declares it explicitly)
- Vue / JS / TS / CSS / SCSS / shell → EUPL-1.2 + Conduction
- Config / data / markdown / JSON / YAML / XML / TOML / img / l10n /
  lockfiles → CC0-1.0 (standard Nextcloud-app treatment)

Template-matching shape — same structure as nextcloud-app-template's
REUSE.toml, year bumped to 2024 (MyDash inception) instead of 2026,
and `**/*.toml` added to the CC0 block so REUSE.toml itself is
covered.

**info.xml**: Nextcloud max-version 33 → 34. Template supports 34; no
reason MyDash shouldn't.

Skipped the Makefile dev-link symlink helper from the template — the
template needs it because the repo is cloned as
`nextcloud-app-template` but the <id> is `app-template`. MyDash's
<id> is `mydash` and the repo is cloned as `mydash`, so the symlink
trick is a no-op here.

* docs: Add architecture, ADR index, ADR compliance audit

Three new markdown docs wire into the Docusaurus site (autogenerated
sidebar picks them up automatically):

- `docs/architecture.md` — component map (Controller/Service/Mapper
  layers with concrete class counts), request flow for dashboard
  update, auth posture, DI posture, capability table mapping to
  openspec/specs/, and an explicit "does NOT do" list covering the
  ADRs that are legitimately N/A for MyDash.
- `docs/adr/README.md` — names the canonical ADR location
  (ConductionNL/hydra/openspec/architecture/) and explains why app
  repos no longer carry stale copies (decidesk #71 false-positive
  drift lesson). Quick index of the 23 ADRs.
- `docs/adr-audit.md` — per-ADR compliance matrix for MyDash as of
  2026-04-24. 25 compliant / 4 partial / 2 gaps / 14 N/A. Each row
  carries a status + evidence + link to fix when applicable.

Captures the verified findings from the two-agent audit run at the
start of this PR:

- 0 service-locator escapes in lib/ (fixed in commit 5712903)
- `ResponseHelper::error` no longer leaks (commit 9be1255)
- 72 PHP files have clean EUPL-1.2 headers (commit ab9fc70)
- 9 @nextcloud/vue imports swapped (commit 222cf3f)
- REUSE.toml + max-version bump (commit b5ca3a4)

The two remaining `❌` gaps — @SPEC annotation pass + Newman
collection — are flagged for OpenSpec changes + Hydra pickup in the
next commit.

Corrects the earlier audit's false-alarm IDOR claim (agent 2 stopped
at controller null-check, didn't trace through to the service-layer
ownership verification). ADR-005 per-object auth is actually PASS
once you follow DashboardApiController::update → PermissionService →
DashboardService's ownership guard.

* spec: Two OpenSpec changes for the remaining ADR gaps

Converts the two ❌ items from docs/adr-audit.md into formal OpenSpec
changes so Hydra's pipeline can pick them up once this PR merges.

**spec-annotation-pass-2026-04-24/** — Close the ADR-003 `@spec` tag
gap. Runs `/opsx-annotate mydash` against the 66 methods already
classified by `openspec/coverage-report.md` (61 Bucket 1 + 5 Bucket
2b from the legacy-widget-bridge retrofit). Design.md resolves the
3 NEEDS-REVIEW flags from the coverage scan:

- `DashboardResolver::getEffectivePermissionLevel` vs
  `PermissionService::getEffectivePermissionLevel` — both tag with
  `@spec permissions/spec.md#requirement-effective-permission-level`;
  PermissionService is the authoritative impl, DashboardResolver is
  a thin delegator.
- `MyDashAdmin::getForm` + `MyDashAdminSection::getID` — treat as
  plumbing (Nextcloud admin-UI boilerplate, no domain behaviour),
  skip `@spec` annotation with an explanatory docblock note.

**newman-integration-suite-2026-04-24/** — Close the ADR-008 Newman
gap. Adds `tests/integration/mydash.postman_collection.json` covering
all 17 OCS endpoints across 6 folders (Health+Metrics, Dashboards,
Tiles, Widgets, Rules, Admin). Fixture setup/teardown pattern avoids
test pollution; admin-vs-member auth branch tests verify
authorization posture. `.github/workflows/code-quality.yml` already
has `enable-newman: true` — the collection is all that's missing.

Each change has proposal.md + design.md + tasks.md + specs/README.md
explaining that they're annotation-only / test-only (no Requirement
deltas needed). Format mirrors the archived retrofits in
openspec/changes/archive/.

GitHub issues + Hydra dispatch handled in a follow-up — spec files
land on development first so Hydra can locate them.

* chore: update SBOM

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
rubenvdlinde added a commit that referenced this pull request May 3, 2026
…/nextcloud-vue (ADR-004) (#71)

Two stragglers added by widget-add-edit-modal (#65/#68 runtime-shell) and
widget-context-menu (#60) before ADR-004 landed via #34. Swap is mechanical:
NcButton + NcEmptyContent are already exported by @conduction/nextcloud-vue
and used elsewhere in the codebase (TileCard, WidgetRenderer, AdminSettings).
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