Skip to content

Release: merge development into beta - #712

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

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

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 6, 2026

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.

github-actions Bot and others added 30 commits September 6, 2026 12:45
…260906124504

chore(sync): carry beta back into development
hydra-gates v1.15.0 -> v1.16.0
nc-vue      - -> 2.37.0

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Bumps [sass](https://github.com/sass/dart-sass) from 1.103.1 to 1.104.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](sass/dart-sass@1.103.1...1.104.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.104.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.12 to 2.2.13.
- [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-version: 2.2.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

Bumps [conduction/hydra-gates](https://github.com/ConductionNL/.github) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/ConductionNL/.github/releases)
- [Commits](ConductionNL/.github@v1.16.0...v1.16.1)

---
updated-dependencies:
- dependency-name: conduction/hydra-gates
  dependency-version: 1.16.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#676)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.68.0 to 8.69.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.69.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…n the test bootstrap (#719)

On 2026-09-08 a PHPUnit run in openregister took 19 GB of RAM because the
test bootstrap loaded lib/base.php from a source tree that was never
installed. base.php declares OC and builds OC::$server before it throws
Not installed, and that typed static cannot be undone, so every
OC::$server->get() in the code under test autowired app services from
scratch until memory ran out. The CLI php.ini has memory_limit=-1, so
nothing stopped it.

- phpunit.xml and phpunit-unit.xml: hard memory_limit=2G with a comment
  carrying the measured peak (69 MB for the Unit suite without coverage).
- tests/bootstrap.php: decide the Nextcloud root once, up front, through
  buildiq_nc_root_is_installed(), which requires config/config.php to be
  non-empty and declare installed => true. A bare source tree prints one
  STDERR line and the run continues in pure-unit mode; the Doctrine
  placeholders now key off the same decision, so a bare tree gets them
  just like CI does. If base.php is loaded and throws, print the reason
  and exit(1) instead of continuing half-booted.
- tests/bootstrap-unit.php: same guard, keeping BUILDIQ_SKIP_NC_BOOTSTRAP.
- composer.json: psalm gets --memory-limit=2G, both phpmd invocations run
  under php -d memory_limit=2G.

Co-authored-by: Conduction Release Bot <[email protected]>
…global server (#721)

Four DI-built services reached OpenRegister's credential services through
\OCP\Server::get() at call time. Outside a booted Nextcloud that lookup
autowires from scratch and can recurse through a constructor cycle until
memory runs out (19 GB on 2026-09-08 in openregister); inside DI-built
classes it is a hidden dependency a unit test cannot control.

GitHubAppSyncService, GitHubCatalogService, GitHubPushService and
VirtualAppCredentialRegistrar now take Psr\Container\ContainerInterface
in their constructor and resolve the broker (and the two credential
services) through it. The lookups stay dynamic by class-name string
behind the same class_exists guards, with the same try/catch and
fail-closed or fall-back-to-anonymous behaviour as before, because they
are another app's classes and must keep working when that app is older
or absent.

Tests hand in a container mock that resolves nothing, which is exactly
the situation the fail-closed tests already relied on when Server::get()
had no container to answer from. The three StaticAccess rows the phpmd
baseline carried for these files are stale now and are removed.

Fleet sniff (GlobalContainerLookup) on lib: 4 before, 0 after.

Co-authored-by: Conduction Release Bot <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
* feat(openspec): case-page-layout-per-case-type

* feat(openspec): registration-form-builder

---------

Co-authored-by: Conduction Release Bot <[email protected]>
… suite (#725)

The bootstrap guard added earlier today refuses to load lib/base.php unless
config/config.php declares installed => true, and that part stays. It also
made the bootstrap exit(1) when the tree IS installed and base.php throws
part-way, which was the wrong trade: on humaniq that turned all six PHPUnit
legs red on a suite that passes, because base.php dies on a Doctrine constant
the app vendor and the server disagree about.

Reaching a half-built container needs an autowiring lookup, this app has none
in lib, and phpunit.xml caps a run at 2G anyway. So the catch now says plainly
that the container is unreliable and lets the pure unit tests run. The not
installed branch still refuses, unchanged.

Follows humaniq PR 392.

Co-authored-by: Conduction Release Bot <[email protected]>
gate-113 exclusion-evidence fails on development with three exclusions citing a
test nothing in this repo answers to. An exclusion that names a missing file
reads as verified and is not, which is the whole point of the gate.

All three claims are true; only the citations were wrong.

- nldesign-theme-selection, two scenarios: cited vitest
  tests/services/themeValidation.spec.js, which does not exist. The validation
  they describe (unknown source, non-kebab tokenSet, unknown key) is covered by
  tests/composables/useManifestValidator.spec.js, which asserts exactly those
  three error codes.
- openbuild-runtime REQ-OBR-001: cited Newman/manifest-endpoint.spec.ts, a
  .spec.ts name for what is a Postman collection. The manifest fetch, the 404
  for an unknown slug and the auth posture are covered by
  tests/integration/buildiq.postman_collection.json (Manifest endpoint) and
  tests/integration/buildiq-api-contract.postman_collection.json (2. Manifest).

Verified by running the gate the way CI does: 3 findings before, 0 after, and
the count of exclusions naming a test that is present rises from 30 to 33.

Co-authored-by: Conduction Release Bot <[email protected]>
#730)

* docs(specs): cite the tests that exist, not ones that never did

gate-113 exclusion-evidence fails on development with three exclusions citing a
test nothing in this repo answers to. An exclusion that names a missing file
reads as verified and is not, which is the whole point of the gate.

All three claims are true; only the citations were wrong.

- nldesign-theme-selection, two scenarios: cited vitest
  tests/services/themeValidation.spec.js, which does not exist. The validation
  they describe (unknown source, non-kebab tokenSet, unknown key) is covered by
  tests/composables/useManifestValidator.spec.js, which asserts exactly those
  three error codes.
- openbuild-runtime REQ-OBR-001: cited Newman/manifest-endpoint.spec.ts, a
  .spec.ts name for what is a Postman collection. The manifest fetch, the 404
  for an unknown slug and the auth posture are covered by
  tests/integration/buildiq.postman_collection.json (Manifest endpoint) and
  tests/integration/buildiq-api-contract.postman_collection.json (2. Manifest).

Verified by running the gate the way CI does: 3 findings before, 0 after, and
the count of exclusions naming a test that is present rises from 30 to 33.

* fix(newman): declare the quarantine instead of relying on how CI globs

Integration Tests (Newman) is red on development: the quarantined
buildiq-version-diff collection ran and failed its three assertions, the diff
endpoint answering 404 where it expects 200.

The quarantine was never declared. It relied on the shared quality.yml globbing
tests/integration/*.postman_collection.json one directory deep, which the
README stated as fact. On 2026-09-08 that workflow moved to a recursive find,
because its own validate step already counted recursively and the two
disagreed: the job announced 19 collections and ran 18. The moment that landed,
this directory became live.

The collection now opens its description with the @newman exclude directive the
runner honours by name, carrying the reason the README already gave: it asserts
a REQ-OBV-005 grammar ApplicationsController::resolveVersionBlob() does not
implement, and rewriting it to match today's behaviour would turn a product gap
into a fixed-looking test. The quarantine stays a deferral, not a fix.

Verified by running the workflow's own detection over both collections: the
quarantined one reports SKIPPED with the reason, buildiq.postman_collection.json
still reports WOULD RUN. The directive was inserted as text, so the collection
is not reserialised.

---------

Co-authored-by: Conduction Release Bot <[email protected]>
Supersedes #705 and #707, neither of which can land alone.

`@vitest/[email protected]` declares `peer vitest@"4.1.11"` — an EXACT
pin, not a caret — so the two packages can only ever move as a pair.
Dependabot opened them as separate PRs, and each fails with ERESOLVE
while the other sits at 4, which is why both showed 15 failing checks.

This app already carries `vite ^8.2.2` as a direct dependency, so unlike
decidiq, shillinq and filinq it needs nothing else: vitest 5 expects
`vite` resolvable from the project and here it already is. The deadlock
was the whole of it.

Verified locally rather than left to CI: `npm install` clean, then
`npm run test:unit` on vitest 5.0.0 — 1378 tests, all passing, exit 0.

Co-authored-by: Conduction Release Bot <[email protected]>
Was ^2.37.0. Two flags were needed to resolve it and both point at
pre-existing problems rather than this bump:

- `--min-release-age=0`: .npmrc sets a 2-day cooldown and exempts
  @conduction/* via `min-release-age-exclude[]`, but that key is an
  unknown config on npm 11.11.0 (it warns on every command), so the
  first-party exemption gate-84 relies on does nothing.
- `--force`: 2.41.1 declares `peerOptional marked@^12` while this app is
  on `marked@^18`. The lib's peer range needs widening.

Net lockfile effect is 6 removals: the nested marked@12 under the lib,
plus 5 @noble/hashes/pinia entries already pending on the previous
package.json. `npm ci` verified.
…black

`app.css` pinned every `.app-navigation-entry-icon` to
`filter: brightness(0) !important`. That dates from when the entries carried
legacy `icon-*` background-images against a light nav, where NC's active-state
`brightness(0) invert(1)` turned the icon white and made it vanish.

Neither half of that premise holds now. No entry names an `icon-*` class any
more — every one is a PascalCase MDI name registered in src/icons.js, which
CnAppNav renders as an inline SVG filled with `currentColor`, already correct
in light mode, in dark mode and against the active entry's primary-element
background. And the nav takes its background from the theme, so it is not
light in dark mode. All the filter still did was crush the icons back to solid
black, invisible on a dark nav.

It read as anything but a theming bug, because the one icon that stayed white
is the Settings foldout's: NcAppNavigationSettings renders its own toggle
outside `.app-navigation-entry-icon`, so the selector never reached it.

The unrelated `brightness(0) invert(1)` in builder.js stays — that one whitens
an `<img>` over the coloured top header, where white is right in both themes.
The two modes were NcButtons in a bare `role="tablist"` div, with `variant`
flipping between primary and tertiary. "Setup wizard" therefore rendered as a
button beside the primary "Save & preview", read as an action, and appeared to
do nothing when clicked — all it did was swap the panel below. The tablist was
decorative too: no `role="tab"`, no `aria-selected`, no `aria-controls`, and no
tabpanel to point at.

Use CnTabs / CnTab, which exist for this. The Enabled switch and Save &
preview move into `#nav-end`, rendered beside the strip but outside the
tablist so they are not announced as tabs. The error NoteCard is a plain
CnTabs child, which lands at the top of the panel area where the bar used to
put it; `errors` is already mode-scoped, so it needs no duplication. `mode`
stays the source of truth via `:active` + `@click`, so `?mode=setup` still
selects the right tab, and the walkthrough panel keeps its recorder branch —
its old `v-else` ("not setup and not recording") is now just `v-if="recording"`
/ `v-else` inside its own tab.

CnTabs orders the strip by source order, so keeping Walkthrough first meant
moving the setup block after the walkthrough one and re-indenting both for the
extra nesting. Verified content-preserving by diffing the template's
whitespace-stripped sorted lines against the previous revision: the only
differences are the removed bar and the added tab markup.

Note for next time: `eslint --fix` does not repair template indentation here —
`vue/html-indent` is not enabled, so a mis-indented SFC lints green.
…r spelling (#734)

* fix(fleet): resolve renamed fleet app ids instead of hardcoding either spelling

Every cross-app reference in this fleet is a duck-typed runtime lookup.
isEnabledForUser('openconnector') on an instance running integriq does not
error, it returns false, and a /apps/procest/... URL 404s. Each caller here
reads that as "the app is not installed" rather than "I asked for the wrong
name", so the integration goes quiet with nothing logged.

A hard swap to the new literal is the same bug pointing the other way: an
instance still on the published release registers only the old id. So both
halves resolve the id instead, newest first, against IAppManager on the
server and OC.appswebroots in the browser.

Ten sites repointed. Two deliberately left, both verified against the other
app's development branch: integriq no longer serves GET /api/endpoints at
all, and theme.source: 'nldesign' is the design-system id, which
nextcloud-vue's useScopedTheme hard-compares and which is not renaming.

* fix(gates): tag the fleet-id resolver's three functions with @SPEC exclude

gate-16 is diff-scoped, so a local run with no delta base has nothing to
judge and reports the same green as a run that passed. It only failed once
CI compared against development: three new functions in
src/services/fleetAppId.js carried the module-level exclusion but none of
their own.

Each now carries a reason naming the test that actually covers it, rather
than a bare 'infrastructure', so gate-113's evidence check has something
real to resolve.
The page carried two `···` menus — ApplicationDetailActions' own and
CnDetailPage's generic one — plus a duplicate Settings entry, and the record
Edit button sat outside both.

ApplicationDetailActions is now a single CnActionButtons fed by an
`actionDescriptors` computed, with `inline: 6`:

  [Open app ▾] Settings  Setup wizard  Walkthrough
               Manage permissions  Save as template  Edit  [···]

and the menu holds GitHub, Permission history, Support & donation, Export,
Documentation, Delete. Open app keeps `variant: "primary"` and carries the
version list as `children`, so it stays the split button it was; neither
costs an inline slot.

The first block of the computed IS the inline set, since CnActionButtons
promotes the first N collapsible entries in declaration order. That split is
invisible in the descriptor list, so a test pins the exact six — reordering
the computed otherwise moves a control between bar and menu silently. Note
the block reads left-to-right while the header was specified counting
outwards from the `···`.

Descriptors are composed in JS rather than declared in the manifest: each one
drives this component's own modals and the applicationContext gating, which
is what `onSelect` is for. Every method, computed and modal is untouched, and
the old `v-if`s became filters, so role gating behaves as before.

Manifest: the page's generic header chrome is off (showRefresh,
showRequestFeature, showDocumentation, showReportBug) so only this cluster
renders — CnActionsMenu appears whenever ANY of its items resolves, which is
what put the second `···` there. `showEditAction: false` drops the page's
standalone Edit; the cluster renders it instead, driven by the
`openEditForm` the #actions slot scope hands down.

src/icons.js gains the ten icons this cluster names. Descriptor icons are
STRINGS resolved by CnIcon through the registry, so an unregistered name
renders the help-circle fallback rather than the icon.

The Settings spec now asserts on the descriptor's `onSelect` — the old
`[data-test="app-settings-action"]` markup is gone, but its intent (Settings
must route through onSettingsOpen, not a bare assignment) is unchanged.
The segments inside `.ob-detail-header__pill-group` had their corners shaved
off while sitting with a gap down each side. Neither came from the group's
own `border-radius: 999px`.

Nextcloud's core stylesheet styles EVERY plain <button> —
`button:not(.button-vue, [class^="vs__"])` in core/css/inputs.scss — with its
own `border-radius: var(--border-radius)` and, one nesting level deeper,
`margin: 3px; margin-inline-start: 0`. Both landed on these segments. Inside
a pill-shaped `overflow: hidden` group that is exactly the two symptoms at
once: the parent's curve clips a child rounded to a different radius, and the
3px margin holds the child off the group's edges.

Make the group's rounding the only rounding — square children, clipped by the
parent — so the active segment fills its end of the pill cleanly.

The parent in the selector is load-bearing: NC's margin rule scores (0,2,1),
so a scoped `.ob-detail-header__pill` at (0,2,0) loses and `margin: 0` there
would have done nothing at all. With the parent it is (0,3,0) and wins on
specificity rather than on `!important`. Same trap CnTabs documents for its
nav items.

Also drops the `__window-toggle` / `__window-btn` / `__window-btn--active`
rules. The insights time-range toggle moved out of this header into the body
dashboard's KPI strip, and the spec at ApplicationDetailHeader.spec.js:76
already asserts the header renders none of them — the styles were orphaned by
that move and, being scoped, could not match anything. They carried the same
999px + overflow-hidden + plain-button shape, so they would have shown the
identical bug had they been live.
`ConnectorSourcePicker.fetchEndpoints()` calls
`generateUrl('/apps/openconnector/api/endpoints')`. gate-115 reports it as a
cross-app lookup naming a retired app id. The comment beside it already
explained why it is not repointed; this adds the machine-readable half so the
gate stops re-raising an answered question.

Re-verified 2026-09-10 against integriq `development`. The route was RETIRED,
not renamed. Commit 496f2025 (2026-05-20) deleted the `resources` block that
auto-generated `GET /api/endpoints`, and its own replacement comment says
that re-adding a resources entry without restoring the controller methods
produces auto-routes that 500 on hit. EndpointsController now publishes only
handlePath, preflightedCors and logs, and `git log -S "endpoints#index"`
finds no hand-written index route under either name.

So no integriq path answers this call. Correcting the app segment alone
leaves the picker just as empty while the diff reads as a fix. The move to
the OpenRegister object API is its own change, and it depends on the
register-slug rename this repo has not made.

Recorded with `@stale-fleet-app-id exclude`, the shared exclusion convention
gate-115 reads (.github#738).

Comments only. No behaviour changes. The two register-slug findings in
AppChannelApplier and AppRepoSerializer are untouched: those need a probe for
the slug the register actually answers to.

Co-authored-by: Conduction Release Bot <[email protected]>
…ng it (#739)

Buildiq wrote and read Integriq's connector objects at a hardcoded
`openconnector` register. Integriq ships a per-instance repair step that
renames that register to `integriq`, so both slugs are live across the
estate and a literal is wrong on half of it.

The old-slug half is the quiet half. OpenRegister finds no register,
matches no rows, and returns an empty set that is byte for byte what a
healthy empty register returns. No exception, no 404, no log line. On the
local dev instance, which is fully migrated, all five fleet registers
answer to their new slugs and none answers to `openconnector` — so every
connector read and write this app made there was silently reading nothing
and reporting success.

Adopts openregister's published `RegisterSlugResolverInterface`
(ConductionNL/openregister#3571, ConductionNL/.github#739) and branches
on `isResolved()` rather than using the returned value, because a
resolver that invents a slug on a miss has reinstated the defect.

## The app-id check was passing the whole time

`applyConnectors()` already asked `FleetAppId::isEnabledForUser(canonical:
'integriq')` before this change, and it answered yes on exactly the
instances the write was failing on. The app id and the register slug are
moved by two SEPARATE repair steps and either can run first, so
`IAppManager` cannot predict what `openregister_registers` holds. That
half-answer is what made the pin look safe.

So the two half-questions become one: `ConnectorRegisterAvailability`
answers "can this register be written to here, and with which slug",
records the skip itself, and gives the two channels one guard each
instead of two. It also distinguishes the two failures in the report —
`openconnector-unavailable` (no app) from `connector-register-absent`
(app present, register not) — which are different repairs and used to be
reported as neither.

## The guard, watched failing

`RegisterSlugPinTest` is static and repo-wide, not diff-scoped: every one
of these references predates the rename, so none would ever appear in a
diff and a diff-scoped version passes on a repository full of the defect.

Mutation checked. With the literal reinstated at the write, three
assertions reddened and they were the right three: the migrated-instance
case, the never-overwrite assertion in `AppChannelApplierTest`, and the
static guard naming file, line and canonical slug.
`testAnUnmigratedInstanceIsWrittenWithItsOldSlug` stayed GREEN under that
mutation, because on an unmigrated instance the pinned literal happens to
be right. Only the migrated case can catch this, and no test in this repo
was the migrated case. Restored from a byte copy, md5 verified identical.

## Two things found on the way

The existing `AppChannelApplierTest` asserted the register argument was
`'openconnector'`. It passed for as long as the code pinned the same word,
which is what an assertion that copies the implementation buys you.

`tests/bootstrap-unit.php` never loaded the Doctrine constant holders, so
23 tests that mock a query builder errored on that config. Pre-existing
and invisible, because CI runs `phpunit.xml`, whose bootstrap does load
them — the local convenience config was the broken one and the measured
one was fine.

## The contract is not in any tag yet

ConductionNL/.github#739 published the contract at 09:14:31Z, AFTER
v1.17.0 was cut. Measured here: with `^1.17.0` installed,
`ObjectServiceInterface` resolves and `RegisterSlugResolverInterface` does
not. So the bump alone does not make it loadable, and the test bootstrap
now reads the contracts from openregister's own `lib/Contract/` as well as
from the package — gate 67 requires the two to be byte identical, so
either yields the same type. Once a tag ships them, the psalm suppression
this adds can go.

Verified: lint, phpcs, phpmd, psalm, phpstan all exit 0; phpunit 962
tests, 3041 assertions, no failures. Gates 22 and 53 fail locally only
because ajv is not installed (`npm ci`); coverage line reads 78 of 95
declared gates, matching the runner CI resolves at @main.

Co-authored-by: Conduction Release Bot <[email protected]>
`npm ci` reported 31 advisories (1 critical, 13 high). Almost all of them sit
in the newman/postman tree — a devDependency — but the critical one
(handlebars JS injection) and most of the highs had patched releases upstream
that npm never picked up, because postman pins its dependencies to exact
versions. `npm audit fix --force` "fixes" that by DOWNGRADING newman 6.2.2 to
4.6.1, which is both older and still vulnerable; the real fix is to lift the
pinned transitives with overrides.

Overrides added (all resolve to a patched release of the same major, so no
consumer API changed):

  handlebars                     4.7.8  -> 4.7.9   (critical: JS injection)
  node-forge                     1.3.1  -> 1.4.0   (high: 7 ASN.1/signature)
  underscore                     1.12.1 -> 1.13.8  (high: DoS, via httpntlm)
  flatted                        3.2.6  -> 3.4.4   (high: proto pollution)
  fast-uri                       3.1.5  -> 3.1.7   (high: SSRF/host confusion)
  js-yaml                        4.3.1  -> 4.3.2   (high: CPU DoS)
  lodash                         4.17.21-> 4.18.1  (high, nested copies only)
  jose                           4.14.4 -> 4.15.9  (moderate: JWE DoS)
  qs                             6.14.2 -> 6.16.0  (moderate: 4x DoS)
  uuid                           8.3.2  -> 11.1.1  (moderate: bounds check)
  colord                         2.9.3  -> 2.10.0  (moderate: ReDoS-ish)
  postman-request                .48    -> .49     (pulls fixed qs; drops the
                                                    abandoned har-validator)
  postman-collection-transformer 4.1.8  -> 4.1.14

dompurify moves 3.4.14 -> 3.4.15 as a direct dependency, and the override
(`$dompurify`) collapses @toast-ui/editor's nested 2.5.9 onto it.

Left open, deliberately — each one's only "fix" is worse than the advisory:

  @faker-js/faker 5.5.3 (high, GHSA-qxc2-j82w-r537). postman-collection pins
  it to the exact version 5.5.3 — its latest release, 5.3.1, still does — and
  forcing it forward does not degrade newman, it kills it outright at require
  time. Measured, not assumed: installing [email protected] with
  `"overrides": {"@faker-js/faker": "10.6.0"}` in a scratch project makes a
  bare `require('postman-collection')` throw

      TypeError: Cannot read properties of undefined (reading 'city')

  because two things changed under it. The subpath export moved — in v10
  `@faker-js/faker/locale/en` resolves to `{ faker }`, not the faker instance,
  so postman-collection's `var faker = require('@faker-js/faker/locale/en')`
  binds an object with no generators on it. And the v5 API is gone anyway:
  `address` became `location`, and `random.*`, `datatype.number` and
  `phone.phoneNumberFormat` were removed. lib/superstring/dynamic-variables.js
  has 117 faker call sites written against that surface, several evaluated at
  module scope, which is why the failure lands on require and not on use.
  newman and postman-runtime both require postman-collection at module scope,
  so the whole integration-test runner would fail to load.

  Against that, the advisory needs `faker.helpers.fake()` reached with an
  attacker-supplied template; postman-collection never calls `.fake()` at all
  (grepped across lib/), so the sink is unreachable here. devDependency, test
  runner only, never bundled. Revisit when postman-collection unpins faker.

  csv-parse 4.16.3 (moderate). Only 7.0.2 fixes it, and v5 changed the export
  from a callable default to `{ parse }` and renamed `relax`, so the override
  would break `newman run -d <file>.csv` at require time. We never pass -d.

  elliptic (low) and its browserify-sign / crypto-browserify / node-polyfill-
  webpack-plugin parents. No patched version exists upstream in any release
  line. Build-time only, and this app's webpack.config.js replaces
  `plugins` wholesale, so NodePolyfillPlugin never runs and none of it reaches
  a bundle.

Also worth recording, because the audit no longer shows it: @toast-ui/editor's
ESM entry — the one webpack bundles — INLINES DOMPurify 2.3.3 into its own
dist rather than importing the package. The dompurify override therefore fixes
the resolved dependency but not the copy we actually ship. That one needs a
change in @conduction/nextcloud-vue (drop or replace toast-ui); no dependency
pin here can reach it.

Verified: vitest 1383/1383 pass, production webpack build clean, eslint and
stylelint unchanged (warnings only, no errors), `npm ci --legacy-peer-deps`
reproduces the tree, and newman still runs a collection end-to-end against a
local server — requests, urlencoded bodies, sandboxed test scripts and
`{{$guid}}`/`{{$random*}}` dynamic variables all behave.
The dependency-override commit before this one left three advisories open on
purpose, with the reasoning in its commit body. A commit body is the wrong
place for something that has to be re-read every time `npm audit` runs, so it
moves to SECURITY.md, next to the audit gates the release already documents.

Each entry answers three questions rather than just naming the CVE: why the
obvious fix is not available or is worse than the advisory, what specifically
makes the vulnerable code unreachable in THIS app (not merely "it's a
devDependency" — dev tooling runs against real credentials in CI), and the
condition that ends the exemption. The section also states that anything not
on the list is unreviewed and should be treated as a real finding, so a clean
`npm audit` is not the bar and the list expires instead of accumulating.

Covered: @faker-js/faker 5.5.3 (exact-pinned by postman-collection, and
forcing it forward makes `require('postman-collection')` throw — measured,
not assumed), csv-parse 4.16.3 (the fix is a major with an incompatible
export shape, and the vulnerable path needs `newman run -d`, which nothing
here passes), and elliptic (no patched version exists anywhere, and
NodePolyfillPlugin is never registered by our webpack config).

The fourth entry is the one that motivated writing this down. @toast-ui/editor
inlines DOMPurify 2.3.3 into the ESM bundle webpack actually consumes, so it
ships, while `npm audit` reports only the nested *package* — which the new
dompurify override has now silenced. Lifting a dependency and quietly
retiring the last signal that the real copy is still vulnerable is precisely
the failure this file should make impossible to repeat.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
… path

Step 3 told you to "name it Pet Store" the moment you arrived on the Apps
list — no mention of the Add app button or the creation wizard, and describing
a "blank app / empty register" the wizard has never offered. It was anchored to
the Apps page and waited on an `object-created` advance, so it fired on
arrival. Walking the tour against the app turned up the same defect at every
step that involves more than one click.

Two engine constraints shape the fix. `object-created` is dead: CnWalkthrough
listens for a `cn-walkthrough:object-created` window event that nothing in the
fleet dispatches, so a step using it can only be passed by the manual Next.
Every such advance is now a route-match on the navigation the action really
performs. And the dimmer takes clicks everywhere outside its cutout (clicking
it dismisses the tour), so a step whose task spans a dialog, an inline form or
a list row is uncompletable unless the cutout contains all of it. Steps are now
either one click on one element, or a spotlight on the whole working area — the
dialog, the schema designer, the page designer.

The tour goes 8 -> 15 steps, and the added ones are the clicks that were
missing rather than new prose: Add app, the creation wizard, + Add schema, the
add-schema dialog, back to Apps, opening the app, and the page designer. Five
components gained a data-walkthrough-id to anchor a spotlight; the dialogs
still use a .modal-container selector, since NcDialog puts pass-through attrs
on the mask rather than the box.

Three app-level defects had to be fixed for the path to exist at all.

PagesWidget had no header action, so an app with no pages had no route from its
own page into the page designer — every row deep-links an existing page, and
the only other way in was "Edit {version}", nested in a split menu whose
popover NcActions renders on body, outside any cutout. It now mirrors
SchemasWidget's + Add schema.

editVersion() navigated with window.location.href to a route the SPA already
owns, costing a full reload and ending any running tour.

SchemaDesigner.addSchema() did three fallible things after the schema existed —
attach-to-register, refresh-list, stage-result — before navigating to it. A
throw in any of them skipped the navigation, and the caller could not report it
either: SchemaListPanel.onAddConfirm awaits `this.$emit('add', …)`, which in
Vue 3 returns the component instance rather than the handler's promise. The
schema was created, the dialog closed, and nothing else happened. Those steps
are best-effort now and the navigation always runs.

Manifest version 2.2.0; new steps carry sinceVersion 2.2.0 so a returning user
gets them as a delta. Dutch catalogue updated for the changed copy.
The schemas store handed createObjectStore a raw `/apps/openregister`, and
`_buildUrl` concatenates baseUrl verbatim — so every schema call went to
`/apps/openregister/api/schemas/...` with no `/index.php` prefix, and 404s on
any instance that does not have URL rewriting enabled. That constant is the
only base URL the schema designer uses, so the whole CRUD surface was
affected, not just the list.

store.js already generates both of its OR base URLs this way; this one was the
outlier.
The header carried "Open app" plus six inline actions. Settings and Edit are
the two that get used from there; the rest — Setup wizard, Walkthrough, Manage
permissions, Save as template — are occasional and read better in the menu they
already belong to.

`inline` drops from 6 to 2 and Edit moves up beside Settings, since
CnActionButtons promotes the first N collapsible entries in declaration order
and "Open app" is primary, so it is never collapsed and costs no slot. That
leaves Open app, Settings, Edit in the bar with Edit nearest the `···`.
Nothing is removed; the four demoted entries keep their ids and handlers.

The pinned inline-set test moves with it — that split is invisible in the
descriptor list, which is why it is pinned at all.
The four KPI cards navigate to OpenRegister, but nothing said so. The
`cursor: pointer` that was supposed to be the hint sat on the wrapper, while
CnStatsBlock's root is width/height 100% and covers it — so the pointer is
always over the card's subtree and the cursor never changed. That left a hover
tint as the only signal, which you have to already suspect the card is
clickable to discover.

The cursor now goes on the card and its descendants through `:deep()`, hover
paints the border and shadow the library gives its own `.cn-kpi-card--clickable`
so these match every other clickable KPI card in the fleet, and each card
carries a small arrow marker in its corner that is visible at rest. The arrow
says "leaves this page" rather than merely "interactive", which is what these
actually do.

Two attributes on the same wrappers were wrong while I was in there. The role
was `button` but only Enter was handled — a button owes Space as well, and
these navigate, so `link` is both the accurate role and the one whose keyboard
contract the existing handler already satisfies. And role/tabindex/title were
set unconditionally, so a card with no register was still focusable and
announced as a control that does nothing; they are now bound to the same
condition as the click.
rubenvdlinde and others added 30 commits September 22, 2026 17:35
…der-ui

feat(forms): build a registration form in the interface, and enforce the rules that were enforced on nothing
`Inline IF statements are not allowed` is an ERROR in the shared
hydra-gates ruleset, and RegistrationFormTargetSchemaReader shipped two
of them in #905. That reddened `PHP Quality (phpcs)` on development and
took `Quality Report` down with it.

Verified with the same ruleset, by exit code:

  the file as merged   exit 2, 'Inline IF statements are not allowed'
                       at 162 and 195, which is exactly what CI reported
  this commit          exit 0, and exit 0 again for the whole ruleset
                       with warnings suppressed

The behaviour is unchanged: both ternaries are rewritten as the if and
the fall-through return they already were.
…ema-reader

Spell the two returns as statements, the way phpcs requires
Bumps [twig/twig](https://github.com/twigphp/Twig) from 3.28.0 to 3.29.0.
- [Release notes](https://github.com/twigphp/Twig/releases)
- [Changelog](https://github.com/twigphp/Twig/blob/3.x/CHANGELOG)
- [Commits](twigphp/Twig@v3.28.0...v3.29.0)

---
updated-dependencies:
- dependency-name: twig/twig
  dependency-version: 3.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Dexie must be the same version in every bundle on a page: openregister's
integration-global bundle loads everywhere, and two versions stop the app
from mounting. Pinned exact, moving together with ConductionNL/openregister#3788.
…nly, and name the owning repo on sibling rows
docs(parity): buildiq capability matrix, 208 rows against five low-code builders
…atrix

acc-row-level owner openregister to buildiq; form-hidden-presets partial to yes
via portaliq's PortalFormBindingResolver; form-builder note narrowed to
FormFieldBuilder (buildiq#913).
…ctions-2-2026-09-26

fix(parity): apply round-2 cross-lane corrections to the capability matrix
…ource pack

Rows mined from TenderNed requirements, competitor changelogs, roadmaps and
open feature requests, each with origin and originUrl; buildiq's column rated
from the code with the renderer hop. Competitor cells on the new rows stay
unknown until the source readers reach them.
…ight rows it proposed

The nocobase reader rated all 242 rows from the source (93/55/58/1 on the
207 matrix rows); correction packs apply last. Eight capabilities found in
the source and missing from the matrix become competitor-derived rows,
rated for buildiq from the code.
…6.0, sources objects

nocobase: source read plus a lab drive of the one row the source left open
(ux-keyboard, partial); edition rule in readHow. budibase: all rows from the
source with licence gates named. Six budibase-proposed and eight
nocobase-proposed capabilities join as competitor-derived rows. First
appsmith, mendix and power-apps packs folded.
…t proposed

All rows rated from the source with the ce/ee plan rule in readHow; five
capabilities found in the source join as competitor-derived rows,
back-filled for buildiq, nocobase and budibase from their code.
…d all sources objects

Every cell left unknown and all 54 new rows rated from public docs (no trial
accounts); sources, readHow and TenderNed tenders recorded for every
competitor. Strict verify shows only the unknown census (78).
…rce-read

feat(parity): wave 5, competitor source reads, a NocoBase drive, sources and 54 new rows
…e field tasks lack

The My approvals widget filtered task rows on step.role, which OpenRegister
tasks do not carry (they name candidateGroups and candidateRole), and sent
status, which the task inbox ignores. Every row was dropped. Read the pooled
and assigned scopes with isTerminal=false, which the server narrows to the
viewer, and show the task's displayTitle. The test fixtures now use the row
shape TaskInboxService::row() returns.

Fixes #936
…hape

fix(approvals): list open tasks in the My approvals widget again
…due or due soon

The My approvals widget listed tasks without their due date although every
task inbox row carries dueAt, expiresAt, overdue and daysUntilDue. Each row
now shows its deadline, the list is sorted soonest first across both inbox
scopes, and a task is flagged Overdue or Due soon in words, so the warning
does not rely on colour. The logic-task-deadline-warning parity row records
the warning half; progress reporting stays open.

Fixes #937
feat(approvals): show the deadline and warn about overdue and due-soon tasks
… per form

Every form a maker opened to the outside was anonymous: the form page path
hard-coded minTrust 0 (not a value portaliq knows) and the registration form
had no trust field. Both paths now carry a per-form sign-in level from
portaliq's value set (low, substantial, high; absent is anonymous). A repeat
form page save that names no level keeps a stored level instead of writing
the anonymous entry back over it, and a form that asks for a sign-in gets no
OpenRegister public create grant, since portaliq writes past RBAC itself.
Parity rows form-digid-login and form-public updated.

Fixes #935
The rule pushed RegistrationFormValidator over the phpmd class complexity
threshold (52 of 50).
feat(forms): a per-form DigiD or eHerkenning sign-in level on both publish paths

This branch has not been deployed

No deployments
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.

3 participants