Skip to content

Release: merge development into beta - #67

Open
github-actions[bot] wants to merge 90 commits into
betafrom
development
Open

Release: merge development into beta#67
github-actions[bot] wants to merge 90 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

rubenvdlinde and others added 3 commits May 2, 2026 17:05
release: Promote external-source installs + PAT + discovery to main
…pdates

Bumps the npm_and_yarn group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.3.1` | `7.3.3` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) | `1.1.12` | `1.1.14` |
| [picomatch](https://github.com/micromatch/picomatch) | `4.0.3` | `4.0.4` |
| [picomatch](https://github.com/micromatch/picomatch) | `2.3.1` | `2.3.2` |
| [picomatch](https://github.com/micromatch/picomatch) | `4.0.2` | `4.0.4` |
| [axios](https://github.com/axios/axios) | `1.13.5` | `1.16.0` |
| [dompurify](https://github.com/cure53/DOMPurify) | `3.3.1` | `3.4.2` |
| [flatted](https://github.com/WebReflection/flatted) | `3.3.3` | `3.4.2` |
| [immutable](https://github.com/immutable-js/immutable-js) | `4.3.5` | `4.3.8` |
| [lodash](https://github.com/lodash/lodash) | `4.17.23` | `4.18.1` |
| [postcss](https://github.com/postcss/postcss) | `8.5.6` | `8.5.14` |
| [yaml](https://github.com/eemeli/yaml) | `2.8.2` | `2.8.4` |



Updates `vite` from 7.3.1 to 7.3.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.3/packages/vite)

Updates `brace-expansion` from 1.1.12 to 1.1.14
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](juliangruber/brace-expansion@v1.1.12...v1.1.14)

Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@4.0.3...4.0.4)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@4.0.3...4.0.4)

Updates `picomatch` from 4.0.2 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@4.0.3...4.0.4)

Updates `axios` from 1.13.5 to 1.16.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.13.5...v1.16.0)

Updates `dompurify` from 3.3.1 to 3.4.2
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](cure53/DOMPurify@3.3.1...3.4.2)

Updates `flatted` from 3.3.3 to 3.4.2
- [Commits](WebReflection/flatted@v3.3.3...v3.4.2)

Updates `follow-redirects` from 1.15.11 to 1.16.0
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](follow-redirects/follow-redirects@v1.15.11...v1.16.0)

Updates `immutable` from 4.3.5 to 4.3.8
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](immutable-js/immutable-js@v4.3.5...v4.3.8)

Updates `lodash` from 4.17.23 to 4.18.1
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](lodash/lodash@4.17.23...4.18.1)

Updates `postcss` from 8.5.6 to 8.5.14
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.6...8.5.14)

Updates `yaml` from 2.8.2 to 2.8.4
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](eemeli/yaml@v2.8.2...v2.8.4)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.3
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.14
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: axios
  dependency-version: 1.16.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: dompurify
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: immutable
  dependency-version: 4.3.8
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.14
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: yaml
  dependency-version: 2.8.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Defense against supply-chain attacks (e.g. shai-hulud, nx-style
compromised publishes) by blocking install of any package version
published less than 24h ago.

- .npmrc: new; `min-release-age=1` (npm 11.5+ native; older npm ignores it)
- .github/dependabot.yml: add `cooldown.default-days: 1` to the existing
  npm entry, with @conduction/* excluded so first-party releases reach
  this repo immediately

For release-day consumption of fresh @conduction/* deps, use
`npm install --min-release-age=0 @conduction/pkg@x.y.z`.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 9d63ff6

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

Quality workflow — 2026-05-25 20:11 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ dcf32b7

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

Quality workflow — 2026-05-25 23:49 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 25 commits May 29, 2026 09:09
Generated from Conduction/nextcloud-app-template's docs/ structure with
per-app config (title, tagline, url, projectName, editUrl, Codeberg link)
substituted in docusaurus.config.js. Live at https://app-versions.conduction.nl.

Scaffolded by autonomous fleet docs rollout to give every Conduction app
a working Docusaurus site that the central documentation workflow can keep
deploying via the existing wrapper at .forgejo/workflows/documentation.yml.
… directory with 11 updates' (#1) from dependabot/npm_and_yarn/npm_and_yarn-359624454c into main

automerge: app-versions#1
Production-readiness hardening. Psalm now reports 0 errors across lib/ while
the 91-test unit suite, php-cs-fixer, and php -l stay green.

- Add tests/stubs/server-internals.php (wired via psalm.xml <stubs>) declaring
  the runtime-only Nextcloud/transitive classes the installer reuses (OC, OC_App,
  OC\Archive\TAR/ZIP, OC\Files\FilenameValidator, OC\AppFramework\Bootstrap\
  Coordinator, OC\DB\Connection/MigrationService, phpseclib\File\X509, PEAR_Error,
  Doctrine\DBAL\Schema\Table). These exist at runtime but are not in nextcloud/ocp.
- Migrate deprecated APIs: IConfig::get/set/deleteAppValue -> IAppConfig,
  Util::getVersion() -> ServerVersion::getVersionString(),
  IAppManager::getInstalledApps() -> getEnabledApps(),
  OCP\Files::rmdirr() -> private recursive-delete helpers.
- Tighten boundary types from parsed info.xml / json_decode (is_* guards, casts,
  precise array shapes) to clear the Mixed* cascade.
- Mark DI/route/event entry points with @psalm-api to remove findUnusedCode
  false positives; delete genuinely-dead members (findOwnedBy, two toArray()).
- Fix latent bug in InstallerService: the 3-arg version_compare() returns bool,
  so the `=== 0/1/-1` comparisons were always false (dead already-installed
  early-return + inverted upgrade/downgrade labels). Now correct.
- exclude tests/stubs from php-cs-fixer.

Update Source/Discovery unit-test mocks for the IAppConfig constructor changes.
Complete the GitHub->Codeberg migration in the docs site (the app code +
metadata were already retargeted). Swaps github.com/ConductionNL/app-versions
-> codeberg.org/Conduction/app-versions and the browse-path scheme
/tree/<branch>/ -> Codeberg's /src/branch/<branch>/, plus the navbar/CTA
labels (GitHub -> Codeberg) and the org contact link.

Genuine GitHub-source feature references (GitHub releases as an install
source, private-repo PATs, owner/repo examples) are intentionally kept.

Files: docusaurus.config.js, src/pages/index.js, intro.md, static/llms.txt,
tutorials/admin/{01-admin-settings,02-rollback,03-github-source}.md
…lize recovery

Improve how App Versions reports and handles install failures (issue #10):

- Structured failure payloads: every failed install returns stage, category
  and an actionable hint regardless of the debug toggle, with category-driven
  HTTP status (409/422/502) instead of a blanket 500 (FailureClassifier).
- Pre-flight environment checks: getInstalledApps() flags non-writable /
  dev-checkout app folders (manageable + warning), and installAppVersion()
  fails fast with preflight_permission before downloading (EnvironmentCheck).
- Finalize-phase recovery: both installers retain the .appversion-backup until
  finalize() succeeds and restore on failure; new installStatus taxonomy
  installed / reverted / installed-but-broken (InstallFailure), surfacing
  honestly that DB migrations cannot be auto-rolled back.
- Frontend: prefer the structured backend message/hint over the OCS meta
  message, render stage/category/hint, and show a warning badge on
  non-manageable app cards.

Adds unit tests for the classifier, environment check and orchestrator
outcomes. Refs #10.
…, surface-external-sources-ui

Planning artifacts (proposal/design/specs/tasks/plan.json) for the three-change
program that moves App Versions into admin settings, adds Codeberg as a forge
(generic forge abstraction, access-token auth), and surfaces GitHub+Codeberg
source/token/trusted-allowlist management in the admin-settings UI.

Tracked on Codeberg as issues #11, #12, #13. Discovery UI and OAuth auth are
deferred. Refs #11 #12 #13.
Relocate the app from the top-nav navigation entry to a Nextcloud admin
Settings section (issue #11):

- Add Sections\AdminSection (IIconSection) + Settings\Admin (ISettings),
  registered via appinfo/info.xml <settings>; reuses the existing Vue SPA
  template as the settings form body (empty renderAs).
- Remove the <navigations> block from info.xml.
- Remove PageController + its FrontpageRoute (GET /): the UI is now served only
  via the admin settings form, so non-admins have no page shell to load.
- Unit tests for both classes.

Refs #11.
Generalize the GitHub-specific release/auth code into a forge abstraction so
GitHub and Codeberg (Forgejo) share one driver, validator and trust model
(issue #12). Auth is via access tokens.

- Forge value object + ForgeRegistry (github, codeberg configs: api base, auth
  scheme Bearer|token, scope-header presence, token-create URL).
- GithubReleaseSource → generic ForgeReleaseSource(forge); GitHub behaviour
  unchanged, Codeberg lists/resolves via the Forgejo API.
- SourceBinding gains a `forge` field (default github); id is now
  {forge}:owner/repo; legacy {kind:github-release} rows load as github.
  Adds SourceBinding::codeberg().
- SourceRegistry resolves both forges to one driver; parseSourceId/listAvailable
  handle codeberg.
- TrustedSourceList: forge-qualified patterns; legacy bare patterns normalize to
  github:; default now github:ConductionNL/* + codeberg:Conduction/*.
- PatValidator is forge-aware (Codeberg via Forgejo /user, accepted with an
  unverifiable-scope warning). Pat gains a `forge` column (migration
  Version1001Date20260609120000, default github) + KIND_FORGE_TOKEN.
- PatResolver matches a token's forge to the binding's forge.
- PatDeeplinkBuilder gains a Codeberg token-create deeplink.

Full backward compatibility for persisted GitHub bindings, source-ids and PAT
rows. Discovery and OAuth remain out of scope. Refs #12.
Backend for surface-external-sources-ui (issue #13), part 1 of 2:

- InstallerService::addTrustedPattern()/removeTrustedPattern() with curated,
  forge-qualified pattern construction and over-broad-glob rejection (no
  whole-forge or match-everything globs; concrete owner required).
- ApiController: POST /api/trusted-sources and DELETE /api/trusted-sources/{pattern}
  (admin-only, password-confirmed); listing reuses GET /api/sources.
- Forge-aware bind/token endpoints: bindSource, createPat (forge → validate +
  KIND_FORGE_TOKEN) and patDeeplink now accept a `forge` param; PatManager::create
  stores the forge. Backward compatible (defaults to github).
- Unit tests: curated add/remove, dangerous-glob rejection set, non-admin 403.

Frontend (tabbed admin UI + Sources/Tokens/TrustedSources panels + shell swap)
is the remaining part. Refs #13.
…d sources

Frontend for surface-external-sources-ui (issue #13), part 2 of 2:

- Swap the NcContent/NcAppContent app-shell for a settings-section container and
  add an in-component tab bar (Apps / Sources / Tokens / Trusted sources); the
  existing apps→versions→install view becomes the default Apps tab.
- SourcesPanel: bind an installed app to a github/codeberg owner/repo source.
- TokensPanel: list/add/share/delete access tokens with per-forge create deeplink;
  targetPattern derived from forge + owner.
- TrustedSourcesPanel: list/curated-add (with explicit trust confirmation)/remove
  forge-qualified allowlist patterns; surfaces backend rejection messages.
- Shared src/ocs.ts request helpers (GET/write + password confirmation).

Verified with `vite build` (compiles cleanly). Follow-ups for live verification:
richer NC form components (NcTextField/NcSelect) over native inputs, app-wide
t() i18n, eslint (blocked by a pre-existing server-root config issue), and the
manual settings-panel walkthrough. Refs #13.
Browser/HTTP verification found DELETE /api/trusted-sources/{pattern} returning
404 on Apache: with AllowEncodedSlashes Off (the default), the %2F in an
encoded forge-qualified pattern is rejected before the request reaches
Nextcloud, so removing a trusted source always failed.

Switch the pattern to a URL-encoded query parameter
(DELETE /api/trusted-sources?pattern=…), which carries %2F without any server
config change. Re-verified live: add then delete round-trips to 200 and the
pattern is removed. Updates the controller, TrustedSourcesPanel, the non-admin
test, and the spec/design/tasks. Refs #13.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 4aa9224

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

Quality workflow — 2026-07-24 00:26 UTC

Download the full PDF report from the workflow artifacts.

- Lkg::toArray() now declares the precise return shape so
  InstallerService::getInstalledApps()'s docblock matches what psalm
  infers for the enriched app-card array (was MoreSpecificReturnType /
  LessSpecificReturnStatement).
- Drop the always-false ($includeDebug) debug-branch ternary in the
  downgrade-guard early return — the guard only fires when
  !$includeDebug, so the branch was dead (TypeDoesNotContainType /
  RedundantCondition).
- Explicitly type the orphanedMigrations value read off the (loosely
  typed) signed-installer result before assigning it to the payload
  (MixedAssignment).

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 4447430

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

Quality workflow — 2026-07-24 00:35 UTC

Download the full PDF report from the workflow artifacts.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 1bfdad4

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

Quality workflow — 2026-07-24 00:37 UTC

Download the full PDF report from the workflow artifacts.

…from debug (#101)

Gives App Versions a first-class occ surface so provisioning scripts, CI
pipelines, and Docker image builds can reproduce an exact app version —
the capability core's occ app:install lacks (nextcloud/server#36940).

- lib/Command/ListVersions.php — `occ app_versions:versions <appId>
  [--source=] [--json]`, table or JSON output with a per-version
  compatibility marker relative to the installed version.
- lib/Command/InstallVersion.php — `occ app_versions:install <appId>
  <version> [--source=] [--dry-run] [--allow-downgrade] [--json]`,
  delegating entirely to InstallerService::installAppVersion (no
  duplicated install logic) with the documented 0-9 exit-code map and a
  self/core-app trust-context guard. Neither command password-confirms
  (CLI trust context matches core occ semantics).
- InstallerService::isManageableApp() — the self/core-app guard extracted
  from the duplicated inline check in getAppVersions()/installAppVersion()
  into one shared, publicly reusable predicate.
- InstallerService::installAppVersion() gains an explicit optional
  $dryRun parameter, independent of $includeDebug (verbosity only); the
  server-side downgrade guard now checks !$dryRun instead of
  !$includeDebug — fixes a latent bug where debug=1 alone would have
  silently bypassed the guard on a real downgrade once dryRun became
  independent. Omitting $dryRun preserves every existing caller's legacy
  behavior (falls back to $includeDebug).
- ApiController::installVersion reads an independent `dryRun` request
  parameter; `debug=1` without an explicit `dryRun` still implies a dry
  run (deprecated) and the response carries a deprecationNotice.
- src/App.vue splits the single conflated "install dry-run / debug"
  checkbox into two independent toggles (Dry run, Show install debug
  output), each sent as its own explicit request parameter.
- docs/cli.md documents both commands, flags, the exit-code table, and a
  reproducible Docker-build provisioning example.

openapi.json is unchanged — vendor/bin/generate-spec (openapi-extractor
v1.8.7) hard-fails on this app's EUPL-1.2 license unrelated to this
change; the /install endpoint was already undocumented before this PR.

Tests: 396 unit tests green (was 166 on development), including new
coverage for the exit-code map, both commands' guards, the dryRun/debug
resolution matrix, and a regression test for the downgrade-guard fix.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 459f466

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

Quality workflow — 2026-07-24 01:05 UTC

Download the full PDF report from the workflow artifacts.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 80f1292

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

Quality workflow — 2026-07-24 01:07 UTC

Download the full PDF report from the workflow artifacts.

Per-app semver-level auto-update policies (none/patch/minor/all) executed
nightly through the standard installer path, honoring pins, bounded to a
configurable maintenance window, defaulting off, and reporting every
outcome as an admin notification.

- lib/Service/Policy/{Policy,PolicyStore} — policy.{appId} app-config JSON,
  mirroring PinStore/SourceBindingStore's malformed-JSON-is-safe pattern.
- lib/Service/AutoUpdate/CandidateSelector — pure semver-level candidate
  selection (patch/minor/all; pre-release and non-semver versions never
  qualify for patch/minor).
- lib/Service/AutoUpdate/AttemptLedger — auto_attempt.{appId} never-retry
  ledger, pruned to the last 10 entries per app.
- lib/Service/AutoUpdate/AutoUpdateWindow — HH:MM-HH:MM window parsing and
  containment, including midnight-crossing windows.
- lib/Service/AutoUpdate/AutoUpdateSettingsStore — global kill switch
  (default off) + window (default 01:00-05:00) app-config values.
- lib/Service/AutoUpdate/AutoUpdateNotifier + Notifier subjects
  auto_update_success/auto_update_failure.
- lib/BackgroundJob/AutoUpdateJob — daily TimedJob; no-ops when disabled or
  outside the window, skips pinned apps without a source query, installs
  through InstallerService::installAppVersion only (which already writes
  the audit trail), per-app try/catch isolation.
- ApiController: GET /api/policies, PUT/DELETE /api/app/{appId}/policy,
  PUT /api/auto-update/settings (password-confirmed writes, 400 on an
  invalid level or malformed window, 403 non-admin).
- Frontend: PolicySelector.vue (NcSelect per-app policy control with an
  active badge and an "automation disabled" hint), global kill-switch +
  window settings row in the Apps tab, autoUpdateWindow.ts client-side
  format validation.

Unit tests for every new class plus the four new API endpoints and the two
new notification subjects; Vitest coverage for PolicySelector and the
window-validation utility.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 223e40e

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

Quality workflow — 2026-07-24 01:39 UTC

Download the full PDF report from the workflow artifacts.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 1fd3d48

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

Quality workflow — 2026-07-24 01:41 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits July 24, 2026 04:13
…ollback (#105)

Adds a release-artifact cache so a rollback still works when the upstream
download URL has rotted:

- ArtifactCache service persists the verified archive + verification
  metadata (sha256 always; App Store: + signature/certificate) to app data
  on every successful install, pruning beyond artifact_cache_keep (default
  3; 0 disables caching). Best-effort — a caching failure never fails the
  install.
- Both installers fall back to the cached artifact when the source download
  fails, re-running the full verification chain (signed: certificate +
  signature re-verify with the stored materials; external: sha256 +
  standard archive validation; allowlist unaffected — it already runs
  before the download step). A tampered cache entry is discarded and the
  original download error surfaces. Install outcomes carry servedFromCache.
- Version listings stamp cachedOffline per entry (one cache query per
  listing).
- GET /api/cache (summary) + DELETE /api/cache?appId= (password-confirmed
  clear, all apps or one) admin-only endpoints.
- UI: offline badge on version rows, new "Artifact cache" settings tab
  (CachePanel) with per-app and clear-all actions.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 5569ccc

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

Quality workflow — 2026-07-24 02:15 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ dcae18c

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

Quality workflow — 2026-07-24 02:16 UTC

Download the full PDF report from the workflow artifacts.

The occ CLI change (#101) declared symfony/console in the production
require block. Nextcloud server already provides symfony/console (v6.4.32
on NC 34) — occ IS a Symfony Console application — so every app that ships
commands links against core's copy.

Declaring it as a production dependency means a --no-dev package build
bundles a second Symfony Console (plus string/service-contracts and four
polyfills) into the app's own vendor/, which Nextcloud autoloads for
enabled apps. A duplicate console library in an app vendor tree is the
same shadowing hazard that has bricked instances before via bundled
sabre/xml and OCP stubs; there is no upside since core's copy is always
present at runtime.

Moved to require-dev so CommandTester stays available to the unit suite
while a production build ships nothing extra. Verified: composer install
--no-dev leaves vendor/ with no symfony directory, and the full unit
suite (485 tests) stays green with the dev install.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ b449982

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

Quality workflow — 2026-07-24 02:19 UTC

Download the full PDF report from the workflow artifacts.

downgradeGuardHint() passed a string carrying %1$s/%2$s placeholders to
IL10N::t() with no parameter array, then sprintf()'d the result itself.
L10NString vsprintf()s the translated text against exactly the parameters
given, so casting it threw

  ValueError: The arguments array must contain 2 items, 0 given

on EVERY downgrade refusal — the core safety feature of the migration
safety guard. Live symptom: 'occ app_versions:install <app> <older>'
aborted with an unhandled exception instead of the specified refusal, and
the HTTP path would 500 rather than return 409.

The whole unit suite stayed green because five IL10N fakes were written as
'fn (string $text) => $text' — they ignored the parameter array and never
vsprintf'd, so neither the missing parameters nor the unsubstituted
placeholders were observable. Test-fake drift hid a production crash.

- Pass the versions through t()'s parameter array (what it exists for)
- Make all five IL10N fakes faithful to L10NString (vsprintf)
- Add a regression test asserting no %n$s survives in the hint

Live-verified on an isolated nextcloud:34 instance: the refusal now prints
both versions and exits 3 (EXIT_DOWNGRADE_REFUSED) as specified.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ b888d77

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

Quality workflow — 2026-07-24 02:28 UTC

Download the full PDF report from the workflow artifacts.

The app shipped ten capabilities with unit and component tests but no
end-to-end coverage: no Playwright dependency, no config, and only four
`@e2e` tags across 219 scenarios — all of them exclusions on pre-existing
specs. Driving the real UI immediately surfaced product defects that the
green suites could not see.

## Suite

- `playwright.config.ts` — base URL and credentials from the environment so
  the same specs run against a disposable container or CI; serial execution
  because the specs share one settings page and mutate server state.
- `tests/e2e/auth.setup.ts` — logs in once, persists the session, dismisses
  the first-run wizard, and warms the App Store caches so a cold catalogue
  download cannot masquerade as a product failure.
- 27 specs over the settings shell (incl. keyboard navigation and a WCAG
  4.1.2 accessible-name sweep), version listing, release notes, safe mode,
  the downgrade guard, pinning with its audit entries, auto-update policies,
  discovery, and the history/cache/tokens/trusted-source panels.
- `docs/e2e.md` documents bootstrapping an instance and the conventions.
- `data-testid` hooks added to HistoryPanel and CachePanel.

## Defects found and fixed

1. **Discovery returned nothing on any ordinary connection.** The catalogue
   download is ~12.4 MB against a 30 s timeout; it aborted mid-body
   (`cURL error 28`), so every search silently produced zero App Store
   results. Timeout raised to 180 s — the result is cached for an hour.

2. **A 30 MB row in `oc_appconfig`.** The catalogue was cached whole, and
   Nextcloud loads every config value of an app at once, so one discovery
   search degraded *every* request this app makes — a version listing went
   from 1.7 s to over three minutes. Only the seven fields discovery reads
   are cached now (30,722,360 → 74,576 bytes), with a size cap that also
   discards the oversized entry written by older builds.

3. **No caching of App Store lookups at all.** `apps.json` ignores its
   `filter` parameter and returns the entire store, so every version listing,
   advisory correlation and install pre-check refetched it. Resolved payloads
   are now cached per app for an hour, with an explicit request timeout:
   repeat listings went from ~33 s to ~1.7 s.

## Traceability

16 `@e2e` tags across the specs, each naming a spec file that exists. Only
scenarios the suite genuinely drives are annotated — the remaining scenarios
are left untagged rather than papered over with exclusions.

Verified: 488 PHP tests, 58 vitest tests, 27 Playwright tests, eslint clean
(0 errors; the 25 warnings match the development baseline).
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ d5ec369

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

Quality workflow — 2026-07-24 16:26 UTC

Download the full PDF report from the workflow artifacts.

… bugs it caught (#111)

Building a fixture forge (a Forgejo-shaped HTTP double the app can bind to and
install from) unlocked end-to-end coverage of the whole forge surface that
could not be tested against real GitHub/Codeberg. Driving real installs through
it immediately caught three defects that unit tests and prior UI verification
had all missed — because nothing before had ever driven a full install to
completion on Nextcloud 34.

## Product seams (both default to the public host — production unchanged)

- ForgeRegistry reads each forge's api/web base URL from app config
  (`forge.{id}.{api_base,web_base}`), enabling self-hosted Forgejo / GitHub
  Enterprise — and the fixture. +unit tests.
- The four forge/PAT HTTP call sites now derive `allow_local_address` from
  Nextcloud's own `allow_local_remote_servers` system switch (default false)
  instead of a hardcoded false, so an operator pointing a forge at a private
  network flips one standard switch. Exercised end-to-end by the fixture.

## Bugs the fixture caught and this fixes

1. **Every install crashed on NC 34.** InstallFinalizer called
   `OC_App::setAppTypes()`, removed from core — a fatal in the finalize phase
   shared by *both* the App Store (signed) and forge install paths. No install
   completed. Replaced with a public-API replication of the `types` app-config
   write that AppManager still reads.

2. **A finalize-phase Error bypassed restore.** The finalize call was caught as
   `Exception`; the setAppTypes fatal was an `Error`, so it escaped the
   restore/installed-but-broken handling and left the app half-swapped. Both
   installers now catch `\Throwable` around finalize.

3. **TOFU digest enforcement was bypassed by an explicit source override.**
   Installing with `--source` / the API `source` param built a fresh binding
   with no recorded digests, so a rewritten release reinstalled through a
   one-off override silently skipped trust-on-first-use. resolveBinding now
   carries the stored binding's recorded digests when the override names the
   same source; only a genuine rebind discards them. +unit tests.

## Also

- `occ app_versions:install --accept-new-sha` (CLI parity with the API's
  acceptNewSha override).
- `tests/e2e/forge.spec.ts` (10 specs): version-specific install, TOFU record +
  fail-closed + acknowledged-accept, downgrade guard, missing-sibling warning,
  rate-limit, offline-cache rollback + badge. Installs run through `occ`
  (opcache-safe); the fixture control plane rewrites/deletes assets and forces
  statuses. `retries: 1` for the state-heavy serial forge specs.
- 8 new `@e2e` tags on the scenarios these cover.

Verified: 493 PHP tests, 58 vitest, 37 Playwright (27 existing + 10 forge),
eslint 0 errors.

res.writeHead(404); res.end('not found')
} catch (err) {
res.writeHead(500); res.end(String(err))
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ a05a553

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

Quality workflow — 2026-07-25 10:53 UTC

Download the full PDF report from the workflow artifacts.

… bug fixed (#112)

* test(e2e): occ CLI + version-management coverage (47 scenarios)

- tests/e2e/cli.spec.ts (8): human/JSON listing, unknown app, reproducible
  install, downgrade-flag, dry-run, integrity exit code, self-management refusal.
- tests/e2e/version-management.spec.ts (13): bound-source query, forge binding +
  re-bind overwrite, one-off query, dry-run, clean install, rollback, failure
  category → HTTP status, manageable/core card flags, settings placement.
- @e2e traceability: cli-commands 8/8; version-management 39/39 (26 driven +
  13 reason-bearing excludes for paths not reachable in a browser — non-writable
  dest, dev-checkout, finalize-fault injection, password-confirm, real-install
  opcache-503, unreachable App Store, out-of-scope init-swallow).

* test(e2e): app-discovery + audit-trail coverage (40 scenarios)

- discovery.spec.ts: +no-match empty state, +installed-only (API contract).
- audit.spec.ts (new): install recorded (who/what/when), filter by app,
  immutable (no mutation endpoints), non-admin blocked, History tab render.
- @e2e: app-discovery 22/22, audit-trail 18/18 — driven where reachable,
  reason-bearing excludes for provider/cache/PAT-private/retention-job paths.

Running total: 110/219 scenarios annotated.

* test(e2e): external-sources coverage (42/42)

external-sources.spec.ts (13): forge listing, repo-not-found, appId/version
mismatch rejection, digest-without-sibling, failed-install-records-nothing,
acceptNewSha-harmless-on-first-install, rebind-discards-digests, digests-in-
version-list, untrusted-bind-403, curated allowlist add/remove/owner-wildcard/
over-broad-rejection. Remaining scenarios tagged to existing specs or excluded
(TrustedSourceList/ForgeRegistry/driver unit tests, PAT-auth-scheme).

Running total: 145/219 annotated.

* test(e2e): pin enforcement coverage (version-pinning 20/20)

pinning-guards.spec.ts (3): install-over-pin rejected, reinstall-pinned-needs-
no-override, list-pins-with-live-status. Drift/notification/override scenarios
excluded with reasons (monitored-not-enforced NC-core drift, daily reconcile
job, occ lacks --pin/--override-pin, web-install opcache-503).

Running total: 161/219 annotated.

* test(e2e): install-effects coverage (migration-safety/cache/changelog/auto-update)

install-effects.spec.ts (7): last-known-good updated-on-success / preserved-on-
failure, artifact cache populate + clear, forge release body -> changelog,
invalid policy level rejected. Fixture releases now carry a body.
Remaining scenarios tagged or excluded (migration-diff needs an app with
migrations, retention/tamper/write-failure cache injection, changelog range +
truncation vitest/unit, nightly auto-update job, slow all-apps advisory
endpoint — advisory correlation confirmed live: pinned-to-vulnerable).

Running total: 188/219 annotated — only pat-management remains.

* fix(pat): PAT list/resolution 500'd on a NULL shared_with_admins; + e2e coverage

The fixture-backed PAT e2e caught a fourth latent crash. `shared_with_admins`
was created `notnull => false` with no default, and QBMapper omits the field on
insert when it equals the entity default (false) — so every stored PAT persisted
a NULL there. Reading such a row then fatals (`Pat::$sharedWithAdmins` is a
non-nullable `bool`), which 500s `GET /api/pats` AND PatResolver during forge
fetches the moment any token exists — breaking the whole PAT feature on every
database, latent because no test had created and then read a PAT on a live
instance.

Migration Version1004 backfills existing NULLs to false and gives the column a
`false` default + NOT NULL, so an omitted insert stores false. App version → 1.4.1.

pat-management.spec.ts (8): codeberg token accepted with unverifiable-scope
warning, revoked token rejected, no-plaintext-in-API, edit + delete, per-forge
deeplinks (classic/fine-grained/codeberg), Tokens panel redaction, non-admin
blocked. Fixture /user now rejects a "revoked" token.

@e2e: pat-management 31/31 — 12 driven, 19 reason-bearing excludes (github
scope-header validation, private-repo auth, expiry-warning TimedJob, multi-admin
ownership, internal invariants). TOTAL: 219/219 scenarios annotated.

* test(e2e): tidy discovery spec (lint)
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ b8ec9f5

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

Quality workflow — 2026-07-25 19:41 UTC

Download the full PDF report from the workflow artifacts.

…tore stale-if-error cache bug (#113)

Converts 13 gate-19 scenarios from "@e2e exclude … unit-tested" to executed
Playwright/occ tests, driving what the harness could not before:

- background jobs (jobs.spec): auto-update apply+notify, pinned-skip, kill-switch
  no-op, failed-not-retried+notify, PAT expiry warn-once / no-expiry, audit prune,
  pin-drift reconcile (drift + no-drift). Driven via `occ background-job:execute
  --force-execute`.
- PAT validation (pat-validation.spec): classic-PAT scope accept/reject, expiry
  capture, encryption-at-rest, private-repo auth (with/without token), expired-PAT
  skip, cross-owner delete refused, user-deletion sweep. The fixture forge now
  serves X-OAuth-Scopes / token-expiry headers and can gate a repo behind auth.
- faults & cache integrity (faults.spec): migration-diff on downgrade, finalize-
  phase failure reverts, cache retention, untrusted-source cache not served,
  tampered-cache discarded, unreachable App Store surfaces an error.

Bug found by running the full suite together and fixed here:

- AppStoreSource cached the store payload TTL-gated only. When the 1-hour TTL
  lapsed and the store had an outage (observed live: garm3 answering 200 with an
  empty body), every App Store listing blanked out despite a good cached copy on
  disk. Add stale-if-error: on a failed live re-fetch, serve the last cached
  payload regardless of age. Unit-tested (testStaleCacheServedWhenRefetchFails)
  and proven live (72 versions served from stale cache against a dead upstream).

Test-harness robustness:

- auth.setup installs the App Store subject app (`notes`) idempotently, so the
  suite is self-provisioning instead of depending on a manual step.
- the downgrade-guard spec targets the oldest release the store still lists
  instead of a hard-coded 1.0.0 that has since aged out of the catalogue.

Also fixes three pre-existing php-cs-fixer import-order nits (ApiController and
two unit tests) encountered along the way.

gate-19 stays at 219/219; 130 scenarios now backed by executed tests (was ~118).
Full suite: 97 passed, 1 flaky-passed-on-retry, 0 failures.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/app-versions @ 88c02ea

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

Quality workflow — 2026-07-26 09:14 UTC

Download the full PDF report from the workflow artifacts.

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.

4 participants