chore(info.xml): canonicalize PHP min, NC version range, licence spelling - #895
Merged
Merged
Conversation
…ling Aligns appinfo/info.xml with the fleet canonical: - <php min-version="8.3"> — matches the composer require.php constraint (^8.3 fleet-wide) - <nextcloud min-version="28" max-version="34"> — converge the fleet on one NC support range - <licence>agpl</licence> — fix the casing/value drift (fleet had agpl / eupl / EUPL-1.2 / AGPL-3.0-or-later — 4 spellings). Stays on "agpl" workaround per the EUPL store-listing pattern; switch to "EUPL-1.2" once NC 34 is the fleet floor (ConductionNL/.github#98). Per-app fields (<id>, <name>, <description>, <version>, etc.) are preserved. Drift surfaced in https://github.com/ConductionNL/nextcloud-app-template/blob/development/docs/fleet-drift-deeper.md#4-appinfoinfoxml--significant-drift
rubenvdlinde
requested review from
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
May 23, 2026 07:58
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 148/148 | |||
| npm | ✅ | ✅ 672/672 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ |
Quality workflow — 2026-05-23 08:00 UTC
Download the full PDF report from the workflow artifacts.
3 tasks
rubenvdlinde
added a commit
that referenced
this pull request
May 23, 2026
#877) (#908) * feat(rule-editor): extended JsonLogic ops + bespoke action forms (closes #877) Visual condition leaf gains the remaining ops jwadhams/json-logic-php supports: var, cat, +, -, *, /, %, substr, merge, map, filter, reduce, all, none, some, if, missing. The picker now groups them (comparison / arithmetic / string / array / control-flow / negation) and renders the right input slots per op-kind (binary, unary, ternary, if, array-op, merge, var-only). JSON slots get a textarea with parse error feedback; literal slots keep the same coerce()-on-input path. Per-action-type forms replace the JSON-textarea fallback for all 15 canonical rule action types. Each form lives at src/views/Rule/actionForms/<Name>Form.vue and round-trips its slice of `configuration[<type>]` via update:value. RuleActionConfig wires them through ACTION_FORM_MAP; the JSON-textarea fallback stays for any future action type without a bespoke form. Two outliers (`mapping` writes to configuration.mapping bare id; `javascript` writes to configuration.javascript bare string) are special-cased. Also fixes the malformed `<nextcloud-app id="openregister">` line in appinfo/info.xml (collateral from #895's canonicalize sweep) so eslint can parse the project again. * fix(rule-editor): satisfy stylelint rule-empty-line-before
rubenvdlinde
added a commit
that referenced
this pull request
May 27, 2026
* chore: drop dead settings store + phpcbf cosmetic fixes (#885 #889) (#891)
#885 — drop /api/settings 404 spam
Chain-C removed the settings GET/PUT routes (only settings#rebase
remains in appinfo/routes.php) but the frontend store still called
fetchSettings() from App.vue.created(), throwing 404 + unhandled
rejection on every page load. No openconnector code reads from the
settings store after the fetch, so the whole module + its store.js
registration is dead code.
- Remove the useSettingsStore import + created() call from App.vue
- Delete src/store/modules/settings.js (+ empty modules/ dir)
- Trim store.js to an empty barrel (kept so future v2 widget action
stores have a known home per the chain-D2 spec)
#889 Phase 1 — phpcbf auto-fixes
Two minor indentation/blank-line cosmetic fixes that phpcbf picks up
on its own. The rest of the 96-file PHPCS debt needs hand authorship
(inline comment punctuation, grouped @param/@return tags, implicit
comparison rewrites) and stays tracked in #889 for per-file sweeps.
* fix(jobs): jobClass NcSelect + standalone sync picker (closes #886) (#893)
Two wiring bugs caught while browser-verifying #886:
1. jobClass rendered as plain text input — the `field.key === 'jobClass'`
branch sat *after* the widget-based branches in the v-if chain. The
Job schema declares jobClass with widget='text' (the default), so
v-if="field.widget === 'text'" matched first and short-circuited
to NcTextField. Moved the key-based check to the top of the chain.
2. Synchronization picker never appeared even with jobClass set to
SynchronizationAction — the picker was inside `v-if="field.key ===
'arguments' && isSynchronizationJob"`, but the OR Job schema doesn't
expose an `arguments` field. The picker had no field-row to attach to.
Added a standalone block after the v-for that renders when
`isSynchronizationJob && !hasArgumentsField` so the conditional UX
works regardless of whether the schema gains an explicit arguments
field in OR.
Browser-verified end-to-end: opening Add Job dialog → jobClass shows
NcSelect with 6 action-class options → picking SynchronizationAction
swaps in a 6th "Synchronization *" field with NcSelect.
* chore(deps): bump @conduction/nextcloud-vue beta.68 → beta.71 (advances #890) (#894)
Beta.71 dropped the explicit `@nextcloud/vue@9` (Vue 3 line) dependency
declaration from nc-vue's package.json — only `@nextcloud/vue@^8` remains.
This eliminates the headline Vue 2 / Vue 3 invalid-peer noise that
surfaced in #890's CI logs.
Caveats:
- nc-vue@beta.71 STILL pulls `@nextcloud/vue@9.8.0` as a transitive
dependency via some lower-level package, so the SBOM job's
`npm ls --package-lock-only` still exits non-zero. The remaining
problems are upstream-nc-vue packaging issues (tracked in #890):
- rehype-react@7.2.0 peer @types/react missing
- bootstrap@4.6.2 peer jquery missing
- vue-router@4 (under nc-vue) peer pinia@^3 vs root pinia@^2
- Build passes (webpack 5.107.1, 7.16MB bundle, vs 7.20MB on beta.68)
- Browser-smoke-tested: dashboard + all index/detail pages render with
0 console errors
Used --min-release-age=0 to bypass the 24h supply-chain cooldown in
.npmrc (beta.71 was released 2026-05-22T10:31Z, < 24h ago) per the
documented override path for fresh @conduction/* releases.
* chore(info.xml): canonicalize PHP min, NC version range, licence spelling (#895)
Aligns appinfo/info.xml with the fleet canonical:
- <php min-version="8.3"> — matches the
composer require.php constraint (^8.3 fleet-wide)
- <nextcloud min-version="28" max-version="34">
— converge the fleet on one NC support range
- <licence>agpl</licence> — fix the casing/value drift
(fleet had agpl / eupl / EUPL-1.2 / AGPL-3.0-or-later — 4 spellings).
Stays on "agpl" workaround per the EUPL store-listing pattern;
switch to "EUPL-1.2" once NC 34 is the fleet floor
(ConductionNL/.github#98).
Per-app fields (<id>, <name>, <description>, <version>, etc.) are
preserved.
Drift surfaced in
https://github.com/ConductionNL/nextcloud-app-template/blob/development/docs/fleet-drift-deeper.md#4-appinfoinfoxml--significant-drift
* chore(ci): add missing canonical workflows (#896)
Per [nextcloud-app-template/docs/fleet-drift-deeper.md §3](https://github.com/ConductionNL/nextcloud-app-template/blob/development/docs/fleet-drift-deeper.md#3-ci-workflows-githubworkflowsyml),
openconnector was missing 2 of the canonical 10-workflow fleet set.
Adds from the template:
- pull-request-lint-check.yaml (PR-title conventional-commit format)
- spec-validation.yml (run openspec validate --strict)
* style(phpcs): fix lib/Action debt (advances #889) (#897)
Add file docblocks, member-var docblocks, constructor docblocks; correct
inline-comment punctuation and capitalisation; rewrite implicit-true
comparisons; split @return from @param tag groups; inline IF rewritten
to explicit if/else (SynchronizationAction).
Refs ConductionNL/openconnector#889
* style(phpcs): fix lib/Cron debt (advances #889) (#898)
Update file docblocks to canonical Conduction format; add @var on member
variables; convert inline-comment punctuation; switch to named arguments
on parent and self method calls (TimedJob setters, parent::__construct).
Refs ConductionNL/openconnector#889
* style(phpcs): fix lib/Twig debt (advances #889) (#899)
Add file docblocks, class/constructor/method docblocks; split @return
and @throws from @param tag groups; convert inline-comment punctuation;
register \OC\Files\Node\File import ordering (auto-fixed).
Refs ConductionNL/openconnector#889
* chore(modals): drop the EditMapping.vue legacy orphan tree (#900)
The pre-chain-E Mapping/EditMapping.vue (1537 LoC) and its
mappingItem/{Edit,Delete}MappingItem.vue children stayed in the tree
as an "extraction reference" — but the bespoke replacement shipped
in #874 (MappingDetailPage + MappingRulesEditor + EditMappingRuleDialog
under src/views/wrappers/) covers the same UX and these files have
zero import sites since chain-E. Keeping them around just bloats the
modals tree.
Side benefit: this file was the ONLY remaining import site of
`bootstrap-vue` in openconnector. Once the next @conduction/nextcloud-vue
release lands (ncv#346 dropped bootstrap-vue from peerDeps), we can
also drop bootstrap-vue from this app's package.json — tracked in a
follow-up PR.
Updates src/modals/README.md to move the three files to the "Removed"
table with pointers to the chain-E replacements.
* style(phpcs): fix Settings/Sections/Exception/EventListener/Http debt (advances #889) (#901)
11 files, 142 errors -> 0. Adds canonical Conduction file docblocks,
@var on member variables, constructor/method docblocks; switches to
named arguments on $this->/self::/parent:: calls; rewrites inline
IFs ($dom->saveXML() ?: '') to explicit branches; splits long lines
in ViewDeletedEventListener; extracts assignment-in-if; sorts use
imports alphabetically.
Refs ConductionNL/openconnector#889
* chore(deps): bump nc-vue beta.71 → beta.73 + drop bootstrap-vue (closes the open #890 chain) (#902)
beta.72 + beta.73 are the upstream nc-vue releases that ship the fixes
from ncv#342 + ncv#346:
- @nextcloud/dialogs ^7 → ^6 (drops the Vue 3 dep chain)
- linkifyjs + @types/react added as direct deps (closes the
transitive missing-peer chain)
- BTabs/BTab replaced with hand-rolled tabs in nc-vue's two
consumers (CnTabbedFormDialog + CnAdvancedFormDialog), so
bootstrap-vue is no longer in nc-vue's peer deps
With nc-vue@beta.73 in place, openconnector's `bootstrap-vue` direct
dep can go too — it was only here to satisfy nc-vue's peer (#900 had
already removed the one local import site in EditMapping.vue).
Result: `npm ls --json --long --all --package-lock-only --omit=dev`
now exits 0. The SBOM quality gate that blocks dev→beta releases
unblocks cleanly. Closes #890.
Build verified (webpack 5.107.1, 7.1MB bundle). Browser-smoke clean
in the dev container: dashboard + all index/detail pages render with
0 console errors.
* feat(mapping-editor): drag-reorder + live preview (closes #876) (#903)
MappingRulesEditor — wrap each tab's table body in `vue-draggable-plus`'s
`<VueDraggable tag="tbody">`. Each row gets an MDI `drag-vertical` grip
handle on the left and is `tabindex="0"` so ArrowUp/ArrowDown move the
focused row within its tab. Drag-end / keyboard-move both rebuild the
collection in the new order and emit through the same `update-mapping`
/ `update-cast` / `update-unset` channel the existing add/edit/delete
buttons use. Ghost + drag classes give visible feedback; rows hover and
focus with a left-edge accent. Local `mappingDraft` / `castDraft` /
`unsetDraft` arrays stay in sync with the props through deep watchers so
parent-driven re-fetches reset the editor without losing keystrokes.
MappingDetailPage — split the rules card and a new preview card into a
50/50 grid (collapses to single column under 1100px). The preview pane
holds a JSON `textarea` for a sample input and a read-only pretty-
printed pane below; every rule edit OR sample-input edit triggers a
400 ms debounced `POST /api/mappings/test` (lodash debounce). Inflight
state shows a small NcLoadingIcon next to the Output label so it never
blocks edits. Server-side errors surface inline as an NcNoteCard
(`Preview failed (<status>): <message>`). A "Reset preview" button
cancels any pending request and clears both sample and output. JSON
parse errors on the sample input render inline below the textarea
without firing the request.
The combined reactivity signal stringifies `mapping` + `cast` + `unset`
+ `passThrough` + sample input so any of the five reset the debounce
window. Debounced function lives on the instance (created hook) for
proper `.cancel()` on teardown.
Known limitation (tracked separately):
OpenRegister's PUT path does not preserve client-side JSON object key
ordering. The drag-reorder UI reorders the local editor state and
emits the new ordering, but the server response on the subsequent
fetch re-orders the keys (insertion-merge of new keys at end,
existing keys retained in original storage order). Verified via
direct API tests against `/api/objects/openconnector/mapping/<id>`.
A proper fix needs either an `_order` sidecar field honored by
MappingService, or a schema change to store rules as
ordered-array-of-pairs. Out of scope for this PR per the
file-touch boundary (`src/views/wrappers/`).
* style(phpcs): fix small Controller debt (advances #889) (#904)
6 controllers (Health, Pdok, DSO, Consumers, Rules, Settings) moved
from a combined 13 errors to 0. Adds file docblocks where missing,
short descriptions, constructor/method docblocks; switches parent::
and $this-> calls to named arguments.
Larger Controllers (Endpoints/Jobs/Synchronizations/UI/User/etc.)
remain - tracked under #889.
Refs ConductionNL/openconnector#889
* style(phpcs): fix MetricsController debt (advances #889) (#905)
16 errors -> 0. Switches parent:: and $this-> calls to named arguments;
rewrites inline ternaries to explicit if/else for null-fallback logic
in collectSourceMetrics, collectSyncMetrics, collectJobMetrics.
Refs ConductionNL/openconnector#889
* style(phpcs): fix AppInfo/Repair/PdokUpstreamException debt (advances #889) (#906)
3 files moved from 13 errors to 0. Adds canonical file docblock for
AppInfo/Application.php; constructor docblock for InitializeRegister
repair step; named arguments for App::__construct and SettingsService
DI lookups.
Refs ConductionNL/openconnector#889
* feat(sync-editor): JsonLogic conditions + mapping preview + file picker (#907)
Closes #878 — three follow-ups from #872's SynchronizationDetailPage:
- Visual JsonLogic condition builder on the detail page reusing the
RuleConditionGroup from #873; raw-JSON toggle for power users. Schema
declares conditions as array<object> so we wrap/unwrap a single-element
array around the builder's group node on save/load.
- Inline mapping preview pane (SyncMappingPreview) attached under the
Source → Target mapping picker. Debounced POST to /api/mappings/test
with the picked mapping payload and a user-editable sample input.
Collapsible to keep the picker compact when not needed.
- NcFilePicker swap on sourceType=file via @nextcloud/dialogs
getFilePickerBuilder. Free-text path stays editable; picker is additive.
* feat(rule-editor): extended JsonLogic ops + bespoke action forms (closes #877) (#908)
* feat(rule-editor): extended JsonLogic ops + bespoke action forms (closes #877)
Visual condition leaf gains the remaining ops jwadhams/json-logic-php
supports: var, cat, +, -, *, /, %, substr, merge, map, filter, reduce,
all, none, some, if, missing. The picker now groups them
(comparison / arithmetic / string / array / control-flow / negation)
and renders the right input slots per op-kind (binary, unary, ternary,
if, array-op, merge, var-only). JSON slots get a textarea with parse
error feedback; literal slots keep the same coerce()-on-input path.
Per-action-type forms replace the JSON-textarea fallback for all 15
canonical rule action types. Each form lives at
src/views/Rule/actionForms/<Name>Form.vue and round-trips its slice of
`configuration[<type>]` via update:value. RuleActionConfig wires them
through ACTION_FORM_MAP; the JSON-textarea fallback stays for any
future action type without a bespoke form. Two outliers
(`mapping` writes to configuration.mapping bare id; `javascript`
writes to configuration.javascript bare string) are special-cased.
Also fixes the malformed `<nextcloud-app id="openregister">` line in
appinfo/info.xml (collateral from #895's canonicalize sweep) so
eslint can parse the project again.
* fix(rule-editor): satisfy stylelint rule-empty-line-before
* style(phpcs): clean lib/Migration/ debt (advances #889) (#910)
Cleans 400 PHPCS errors across the 20 files in lib/Migration/:
- Canonical Conduction file docblocks.
- Method docblocks (@param, @return) on pre/changeSchema/postSchemaChange.
- Inline comments end in periods.
- Implicit boolean comparisons rewritten as explicit === false/true checks.
- @var member docblocks on class properties.
- Long lines broken to fit the 150-char limit.
No functional changes — pure style/comment cleanup.
* style(phpcs): clean PdokConnector + small Service files (advances #889) (#911)
Cleans 54 PHPCS errors across 6 files:
- lib/Connectors/PdokConnector.php (21)
- lib/Service/StUFFieldMapper.php (3)
- lib/Service/ObjectService.php (9)
- lib/Service/DSOParserService.php (7)
- lib/Service/StorageService.php (14)
- lib/Service/ConfigurationHandlers/ConfigurationHandlerInterface.php (4)
Same recipe as the migration batch: file/class/method docblocks, named
parameters on internal-code calls, no inline IFs, member-variable @var,
explicit boolean comparisons.
No functional changes - pure style/comment cleanup.
* style(phpcs): clean lib/Service/ConfigurationHandlers/ debt (advances #889) (#912)
Cleans 240 PHPCS errors across 6 handler files + the interface:
- ConfigurationHandlerInterface.php (4)
- EndpointHandler.php (40)
- JobHandler.php (32)
- MappingHandler.php (22)
- RuleHandler.php (51)
- SourceHandler.php (24)
- SynchronizationHandler.php (71)
Same recipe: file/class/method docblocks, named parameters on
$this-> calls, explicit boolean comparisons, ternary -> if/else
where the linter flags it, inline-comment terminators.
No functional changes.
* style(phpcs): clean SOAP + Settings + OrganisationBridge (advances #889) (#913)
Cleans 48 PHPCS errors across 3 mid-size Service files:
- lib/Service/SOAPService.php (17)
- lib/Service/SettingsService.php (22)
- lib/Service/OrganisationBridgeService.php (9)
Same recipe as previous batches. No functional changes.
* style(phpcs): clean AuthenticationService + SyncContractProvider (advances #889) (#914)
Cleans 39 PHPCS errors across 2 Service files:
- lib/Service/AuthenticationService.php (13)
- lib/Service/Integration/SynchronizationContractProvider.php (26)
Same recipe: file docblock, named-args on $this-> calls, full
@param/@return/@throws on every method, explicit boolean comparisons.
No functional changes.
* style(phpcs): clean Ui/Jobs/Sources controllers (advances #889) (#916)
Hand-fix 139 PHPCS errors across the three biggest remaining controllers
plus an incidental PHPStan fix in JobsController:
- lib/Controller/UiController.php (57 errors): canonical Conduction file
docblock; constructor docblock + named-args parent ctor; one-line
description + @return tag on each SPA route method.
- lib/Controller/JobsController.php (43 errors): file docblock; constructor
docblock + named-args parent ctor; rewrite implicit-bool / inline-IF /
inline-comment-period sites; named args on JobService::executeJob;
serialize ObjectEntity via jsonSerialize() before returning JSONResponse
(clears two pre-existing PHPStan errors on lines 252 + 297).
- lib/Controller/SourcesController.php (39 errors): file docblock;
constructor docblock + named-args parent ctor; rewrite implicit-bool /
inline-IF / inline-comment-period sites; named args on CallService::call.
phpcs --standard=phpcs.xml on all three files: 0 errors (32 advisory
@spec warnings remain — out of scope for this sweep).
phpstan on all three files: clean.
* feat(dso): port DSO/Omgevingsloket STAM adapter to post-OR-cutover surface (#881) (#915)
* feat(dso): port DSO/Omgevingsloket STAM adapter to post-OR-cutover surface (#881)
Re-enables the DSO STAM koppelvlak by porting ~1400 LoC of adapter logic
from the preserved legacy branch feature/771/dso-omgevingsloket. Because
the legacy services never depended on the deleted per-app Mapper layer
(they operate on in-memory verzoek arrays and the Nextcloud HTTP client),
no Mapper-to-ObjectService rewrite was required — the legacy code ports
as-is once aligned with the openconnector coding standard (named-params,
@spec tags, IClient::get/post uses uri: not url:).
What landed:
- lib/Service/DSOAdapterService.php (930 LoC) — processVerzoek routes by
type (melding / informatieverzoek / vooroverleg / aanvraag), downloads
bijlagen via HTTPS-only IClient with retry + cert support, maps 25+
default activiteitcodes to zaaktypen, resolves samenloop (deelzaken vs
gecombineerd), creates triage zaken for unknown codes, validates client
certificates, probes DSO-LV connectivity, and exposes the configured
API URL via IAppConfig.
- lib/Service/DSOSamenwerkingService.php (243 LoC) — DSO-SWF send/receive
for adviesverzoeken to/from partner organisations identified by OIN.
- lib/Service/DSOStatusService.php (217 LoC) — status push with status
map (ontvangen/in_behandeling/besluit_genomen/afgerond/buiten_behandeling)
and exponential-backoff retry (2s, 4s, 8s).
- lib/Controller/DSOController.php — replaced the placeholder with the
ported controller body; receiveVerzoek now validates the signature,
passes the payload through DSOParserService, tags environment from the
X-DSO-Environment header, and returns HTTP 202 with verzoekId.
- lib/Service/DSOParserService.php — refreshed to the legacy version
(functionally identical to the dev stub but with named-params + @spec
tags throughout, clearing pre-existing phpcs debt).
- appinfo/routes.php — re-enabled POST /api/dso/stam/verzoeken pointing
at DSOController#receiveVerzoek.
Tests (35 tests, 188 assertions, all green via vendor/bin/phpunit):
- tests/Unit/Service/DSOAdapterServiceTest.php
- tests/Unit/Service/DSOSamenwerkingServiceTest.php
- tests/Unit/Service/DSOStatusServiceTest.php
- tests/Unit/Controller/DSOControllerTest.php
Quality gates passing on all new + touched files (PHP 8.3 docker):
- composer lint (php -l) — clean
- composer phpcs (PEAR/Squiz/PSR + custom named-params + spec-tag) — clean
- composer phpmd — clean (suppressions on adapter's
TooManyPublicMethods/ExcessiveMethodLength/LongVariable and the parser's
Cyclomatic/NPath complexity are intentional; the validatePayload sniff
fires on every required-field iteration)
- composer psalm — clean (UnusedParam on validateSignature is suppressed,
$body is reserved for the full HMAC implementation under REQ-DSO-050)
- composer phpstan (level 5) — clean (added getConfiguredApiUrl() to
consume the IAppConfig dep deliberately)
- composer check:routes — PASS (68 routes including the new one)
- composer check:no-legacy-types — clean (no Mapper imports)
Refs: original implementation on feature/771/dso-omgevingsloket
(preserved untouched as the reference branch; not merged or
cherry-picked because chain-C deleted its Mapper dependencies).
Closes #881.
* ci: regenerate docs/features.json from openspec/specs/ [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* style(phpcs): clean EndpointService.php (advances #889) (#917)
Hand-fix all 178 PHPCS errors in lib/Service/EndpointService.php.
Rules addressed:
- Canonical Conduction file docblock
- Constructor docblock with full @param/@return set (13 ctor params)
- Member docblock on UNSET_PARAMETERS class constant
- Method docblocks: parameter ordering (params before annotations like
@NoAdminRequired), missing @return, stale parameter names matched up
to actual signatures (processSaveObjectRule / processErrorRule /
processMapping / processWriteFileRule / processSyncRule /
processFilePartRule / processFilePartUploadRule / checkRuleConditions /
updateRequestWithRuleData / processDownloadRule)
- Implicit-true comparisons rewritten to === true (str_contains / isset /
preg_match / Uuid::isValid / in_array / empty)
- Inline IF rewritten to if/else (reduceExtendKeys init, replaceUuidsInArray
nested-extend branch)
- Inline comments terminated with full-stops, started with capitals
- Named arguments on internal calls (checkConditions, getPathParameters,
transformError, handleSchemaRequest, handleSourceRequest, getHeaders,
rewriteExternalReferences, replaceUuidsInArray, reduceExtendKeys,
replaceInternalReferences, looksLikeXml, getRuleById, all process*Rule
match arms)
- Long lines wrapped (40+ sites: logger messages, addFile / saveObject /
synchronize / authorize* / executeMapping / updateFromArray / find with
extend / generateEndpointUrl signature, JsonResponse error blocks)
- Empty CATCH statements annotated with intent comment
- /* @var */ block-comments now have surrounding blank lines per sniff
- ORObjectService + new ObjectServiceMapperAdapter import aliases to keep
union types within line-length limit
Side-effect cleanup:
- No behavioral changes (the synthetic helper locals for PUT/PATCH
updateFromArray + the explode for extend ternary use the exact same
args as before)
- Net PHPStan delta on this file: -13 (38 errors -> 25 errors). The
residual 25 are all pre-existing class-import issues (Rule/Endpoint in
OCA\OpenConnector\Service vs OCA\OpenRegister\Db\ObjectEntity)
that aren't part of this style sweep.
Verification:
- phpcs --standard=phpcs.xml lib/Service/EndpointService.php : 0 errors
(3 advisory @spec warnings remain, out of scope per phase-2 convention)
- phpstan: 25 pre-existing errors, down from 38 (net improvement)
* style(phpcs): clean ConfigurationService.php (advances #889) (#918)
Hand-fix all 174 PHPCS errors in lib/Service/ConfigurationService.php.
Rules addressed:
- Canonical Conduction file docblock (was generic "OpenConnector Team /
AGPL-3.0 / OpenConnector" header)
- Member docblocks now have short descriptions (registerMapper,
schemaMapper, handlers, mappings)
- Constructor docblock: 9 @param entries now describe each handler/mapper,
added @return
- Per-method docblocks: matched stale @param names, added @return where
missing, split @return/@throws from @param groups (importConfiguration)
- Implicit-true comparisons (isset / empty / is_array / is_string /
str_contains / str_starts_with / in_array / array_key_exists) all
rewritten to === true / === false
- !$x operator (~17 sites) rewritten to === false / empty(...) === false
- Named arguments on every internal call (fetchBySchema, findByUuids,
buildSchemaSlugMaps, exportSource/Endpoint/Mapping/Rule/Job/Sync,
buildRegisterAndSchemaMappings, findByConfiguration, getEntityComponent,
organizeEntitiesByComponent)
- Inline comments terminated with full-stops; capitalized first letter
- Long-line wrap on the findAll() filter arrays
- getEntityComponent: synchronization branch reads sourceType/targetType
via array_key_exists to dodge a PHPStan false-true === narrowing flag
that the === true rewrite would otherwise trigger
- Net PHPStan delta on this file: 0 (3 errors before, 3 errors after) —
the 3 residuals are pre-existing `??` warnings on always-existing keys
Verification:
- phpcs --standard=phpcs.xml lib/Service/ConfigurationService.php : 0
errors (5 advisory @spec warnings remain, out of scope)
- phpstan: 3 pre-existing errors, unchanged
* style(phpcs): clean SynchronizationService.php (advances #889) (#919)
Cleans the file's 558 PHPCS errors to zero, walking through the full 4488-line
service end-to-end. PHPStan baseline preserved (no new errors).
Changes:
- Add canonical Conduction file/class/member-variable docblocks
- Add full method docblocks (@param/@return/@throws) on every method
- Rewrite all inline comments to end in periods (210+ fixes)
- Wrap calls with named arguments ($this->method(name: $arg)) (63+ fixes)
- Replace inline-if/ternaries with if/else blocks
- Replace implicit booleans (!$x → === false, !empty($x) etc.)
- Wrap long function signatures across multiple lines (110+ fixes)
- Extract long inline filter arrays into intermediate variables
- Remove stale commented-out dead-code blocks from previous refactors
- Trim trailing dead block comment after shouldPublishFile()
* style(phpcs): clean UserService.php (advances #889) (#920)
Hand-fix all 122 PHPCS errors in lib/Service/UserService.php.
Rules addressed:
- File docblock rewritten in canonical Conduction form (the old /* */
bare-block-comment header was triggering the file-doc-comment sniff)
- Constructor docblock: descriptions for the 9 ctor params end in
full-stops, @return added
- Per-method docblocks: split @return from @param groupings (10 methods
total), descriptions terminated with full-stops
- buildUserDataArray: 8 method_exists ternaries expanded to if/else
blocks with intermediate locals (emailVerified, avatarScope, lastLogin,
backend, canChangeDisplayName/MailAddress/Password/Avatar). The
inline-IF + implicit-true sniffs were both rule violations
- updateUserProperties: isset/is_string explicit === true, named args on
updateStandardUserProperties + updateProfileProperties
- getCustomNameFields: replaced `?: null` shortcut ternaries with
explicit if/else null promotion
- buildQuotaInformation / getUsedSpaceMemorySafe / getLanguageAndLocale /
getAccountManagerPropertiesSelectively / updateStandardUserProperties /
updateProfileProperties / getDefaultPropertyScope: implicit-true (===
true), !empty -> empty(...) === false, named args on internal calls
($this->setCustomNameFields, $this->getDefaultPropertyScope,
$this->getAccountManagerPropertiesSelectively, $this->getCustomNameFields,
$this->getUsedSpaceMemorySafe), inline-comment full-stops
- getLanguageAndLocale: nested ternary `$language === 'en' ? 'en_US' :
...` expanded to if/else
Verification:
- phpcs --standard=phpcs.xml lib/Service/UserService.php : 0 errors
(6 advisory @spec warnings remain, out of scope)
- phpstan: clean (was clean, still clean)
* style(phpcs): clean SecurityService.php (advances #889) (#921)
Hand-fix all 101 PHPCS errors in lib/Service/SecurityService.php.
Rules addressed:
- File docblock rewritten from `/* */` bare-block form to canonical
Conduction `/**` docblock
- Each `private const` now has its own @var docblock (max attempts, rate
window, lockout duration, progressive-delay base + cap, and the 5
cache-key prefixes — was 2 grouped block comments before)
- Constructor + every public/private method docblock: split @return from
@param, descriptions end in full-stops, named-arg style preserved
- Inline comments (the entire file has annotation-style trailing comments
on the dangerous-pattern + forwarded-header arrays) collapsed into
proper above-the-line comments
- Implicit comparisons (is_array / is_string / empty / preg_match /
filter_var) rewritten with explicit === true / === false / !== false
- Named args on every internal call:
- sanitizeForCacheKey(input: ...)
- logSecurityEvent(event: ..., context: ...)
- sanitizeInput(input: ..., maxLength: ...)
- Long-line wrapping for the array_map closure
- Inline-comment-period sniff on the trailing-comment lines for the
dangerous-pattern + forwarded-header arrays
Verification:
- phpcs --standard=phpcs.xml lib/Service/SecurityService.php : 0 errors
(8 advisory @spec warnings remain, out of scope)
- phpstan: clean (was clean, still clean)
* style(phpcs): clean RuleService.php (advances #889) (#922)
Hand-fix all 95 PHPCS errors in lib/Service/RuleService.php.
Rules addressed:
- Canonical Conduction file docblock added on top
- Member docblock with @var on currentNodeIdIndex / currentRelationIdIndex
/ createdRelationIds
- Constructor docblock with full @param/@return (was missing entirely)
- Per-method docblocks: getPublishPropertyId / processPropertyDefinitionsAndMetadata /
setupOrganizationalFolders / processVoorzieningenData / processNodes /
createRelation / processCustomConnectionsRule / createConnection (new) /
extendExternalUrl: split @return/@throws from @param, descriptions in
full-stops, stale param names matched up to actual signatures
- Inline comments terminated with full-stops; capitalized first letter;
trailing-comment annotations on the SWC type / Object ID / Extern
Pakket / etc property lines collapsed into proper above-the-line
comments
- Implicit-true comparisons rewritten (is_array / is_string / isset /
empty / Uuid::isValid / preg_match) to === true
- Named args on all internal calls (processSoftwareCatalogusRule /
processCustomConnectionsRule arms of processCustomRule, getPublishPropertyId,
processPropertyDefinitionsAndMetadata, setupOrganizationalFolders,
processVoorzieningenData (using the correct `views:` param name —
not `addedViews:`), processNodes (matchIdentificatie / newElementId /
totalNewChildren), getExternalObject (configuration: / schemaId:),
createRelation)
- Long-line wraps on the findAll filter array, the processNodes signature,
and the extendExternalUrl error responses (now split into multi-line
array literals)
- Long-condition end-comments restored via phpcbf
Verification:
- phpcs --standard=phpcs.xml lib/Service/RuleService.php : 0 errors (3
advisory @spec warnings remain, out of scope)
- phpstan: clean (initially produced 2 errors from a typo in the
named-arg conversion — `addedViews:` instead of `views:` — that was
corrected before commit; net delta is 0)
* style(phpcs): clean LegacyToRegisterMigrator.php (advances #889) (#923)
* style(phpcs): clean SoftwareCatalogueService.php (advances #889) (#924)
* style(phpcs): clean CallService.php (advances #889) (#925)
* style(phpcs): clean 7 controllers (advances #889) (#926)
* style(phpcs): clean 7 services (advances #889, closes debt) (#927)
* fix(schemas): rule.conditions accepts object (JsonLogic shape) (closes #909) (#928)
The Rule schema declared `conditions` as `type: array`, but the visual
condition builder (introduced in #873, RuleDetailPage) emits the canonical
JsonLogic top-level object shape, e.g. `{"and": [{"var": ["user.email"]}]}`.
PUT requests carrying that shape were rejected by OR validation with HTTP
400 "conditions must be array". Repro from #909 (create rule, add `var`
leaf in builder, save) now succeeds.
Changes:
- `lib/Settings/openconnector_register.json`
- `rule.conditions`: `type: array, items: object` -> `type: object`
- description updated to point at the JsonLogic shape and note the
evaluator (`jwadhams/json-logic-php`) accepts both shapes; the
schema mandates object to match the builder output.
- rule schema bumped 1.0.0 -> 1.1.0 so the repair-step re-imports.
- top-level register version bumped 1.0.0 -> 1.1.0 so
`ConfigurationService::importFromApp` short-circuit picks the new
descriptor.
Backwards compatibility verified live against the dev container:
- POST / PUT with object-shape conditions: 200 OK (was 400).
- POST without `conditions`: 200 OK, conditions = null.
- PUT with `conditions: null`: 200 OK, conditions = null.
Other schemas in the register still use the legacy `array of object`
shape for their `conditions` columns (endpoint.conditions:148,
synchronization.conditions:383) - those are tail-list shapes, not
JsonLogic, so they stay arrays.
* chore(phpstan): regenerate baseline + exclude SyncContractProvider (advances #848) (#929)
The phpstan-baseline.neon carried 544 entries, of which 403 referenced
files in lib/Db/ that no longer exist on disk — they were removed in
the or-cutover commit (7df241bc) that deleted the legacy entities and
mappers in favour of OR-backed schemas. `reportUnmatchedIgnoredErrors: false`
hid the rot.
Regenerating the baseline against the current source tree produces 141
real entries — a 74% reduction (544 → 141).
Also: lib/Service/Integration/SynchronizationContractProvider.php extends
OCA\OpenRegister\Service\Integration\AbstractIntegrationProvider, which
is only available at runtime via OR's IntegrationRegistry. PHPStan rejects
"extends unknown class" via baseline (must use excludePaths); the file is
therefore added to phpstan.neon excludePaths with a comment explaining why.
Verified: `phpstan analyse` reports 0 errors after the change.
* chore(phpstan): remove 24 unread properties (advances #848) (#930)
PHPStan flagged 24 private properties as "never read, only written" — all
were DI parameters stored on $this but never used by any method. Removing
them eliminates the dead DI and shrinks the phpstan-baseline.neon by 24
entries (141 → 117).
Files touched (each had 1-2 unused DI dependencies removed):
- lib/Action/EventAction.php — CallService
- lib/Controller/{Consumers,Endpoints,Events,Jobs,Mappings,Rules,Sources,Synchronizations}Controller.php — IAppConfig $config
- lib/Controller/{Consumers,Rules}Controller.php — IL10N $l (was unused on these two only)
- lib/Controller/UserController.php — AuthorizationService, OrganisationBridgeService
- lib/EventListener/ViewDeletedEventListener.php — LoggerInterface
- lib/EventListener/ViewUpdatedOrCreatedEventListener.php — SynchronizationService + LoggerInterface
- lib/Service/AuthorizationService.php — IGroupManager (used only in commented-out code), IProvider
- lib/Service/EndpointService.php — IAppConfig $appConfig
- lib/Service/RuleService.php — LoggerInterface
- lib/Service/SOAPService.php — $transport (was a write-through assignment used inline)
- lib/Service/StUFFieldMapper.php — LoggerInterface
- lib/Service/UserService.php — IUserManager
- lib/Settings/OpenConnectorAdmin.php — IL10N
All removals are safe: these classes are auto-wired by Nextcloud's DI
container (no manual registration in Application.php), and constructor
signature changes don't break the wire.
Verified:
- phpstan analyse → [OK] No errors with regenerated baseline
- phpcs lib/ → 0 errors (455 warnings, all pre-existing @spec docblock warnings)
* chore(phpstan): fix docblock types + IRequest migration (advances #848) (#931)
Two related cleanups in EndpointService.php and Service/Helper/FlowToken.php
that together remove 24 phpstan-baseline entries.
1. Fixed @param docblock types that referenced non-existent classes:
- 7x @param Rule -> @param ObjectEntity (these methods take OR ObjectEntity
rule objects; \OCA\OpenConnector\Service\Rule does not exist)
- 2x @param Endpoint -> @param ObjectEntity (same pattern for endpoint objects)
- 1x @param OCA\OpenRegister\Db\SchemaMapper -> @param \OCA\OpenRegister\Db\SchemaMapper
(the missing leading backslash made phpstan resolve the type as
OCA\OpenConnector\Service\OCA\OpenRegister\Db\SchemaMapper which doesn't
exist)
2. Migrated 3 EndpointService methods + 1 FlowToken method + 1 FlowToken
constructor from the internal OC\AppFramework\Http\Request to the public
OCP\IRequest interface (phpstan can't see the internal class). All the
methods only call interface-public methods on the parameter, so the
migration is type-safe.
3. Side effects of the IRequest migration that needed accompanying fixes:
- $request->server -> $_SERVER (IRequest has no public $server property;
this also clears 2 pre-existing baseline entries in EndpointService
for "Access to undefined property OCP\IRequest::\$server")
- getHeader() returns string (not mixed), so `$contentType === null`
was dead code; replaced with `$contentType === ''` (the empty-string
fallback that IRequest::getHeader actually returns for missing headers)
Baseline progress:
- Before this PR: 117 entries (after #930)
- After this PR: 93 entries (-24)
- #848 target <=400: well past it (3 PRs in this session)
Verified:
- phpstan analyse -> [OK] No errors with regenerated baseline
- phpcs lib/ -> 0 errors
* chore(phpstan): fix possibly-undefined variables (advances #848) (#932)
PHPStan flagged several variables that might not be defined when read.
Each is fixed in source — initializing the variable at the top of its
scope, restructuring a dead-branch, or removing a duplicated-after-loop bug.
Files touched:
- lib/Service/MappingService.php (handleCast):
Initialize $unsetIfValue, $setNullIfValue, $countValue to null at top
of the function. They were only assigned inside conditional branches
(str_starts_with($cast, 'unsetIfValue==')) but read unconditionally
inside the switch arms. (-6 baseline entries: 3 for $unsetIfValue, 3
for $setNullIfValue)
- lib/Service/EndpointService.php:
* processLockingRule: add explicit else-branch that returns $data
untouched when the locking action is neither 'lock' nor 'unlock'.
$object was undefined on that path.
* processWriteFileRule:
+ initialize $fileName = null at start of each foreach iteration
(was only set inside `isset($value['filename'])`)
+ drop the duplicate-of-line-1774 `$result[$key] = $file->getPath();`
after the foreach (real bug: $key and $file are undefined if the
catch block ran or the loop didn't execute, plus this overwrites
the last valid mapping with itself)
* line 752: simplify `isset(\$ids) === false || \$ids === null || empty(\$ids)`
to `\$ids === null || empty(\$ids)` — `\$ids` was just assigned the
line above, so the isset() check was dead.
- lib/Service/SearchService.php (parseQueryString):
Initialize \$vars = [] before the foreach. Was returned and passed
by reference without ever being initialized.
- lib/Service/SynchronizationService.php (processSaveObjectRule):
Initialize \$id = null. It was only assigned inside a nested
`if (empty(\$mapping) === false && isset(\$data[…]))` branch but
later read in the unconditional patch-find call.
Baseline progress:
- Before: 93 entries (after #931)
- After: 83 entries (-10)
Verified:
- phpstan analyse → [OK] No errors with regenerated baseline
- phpcs lib/ → 0 errors
* chore(phpstan): kill dead-branch comparisons (advances #848) (#933)
PHPStan flagged a cluster of strict-comparison branches that can never
execute given the surrounding types. Each is removed or restructured
in source code; one was an actual bug.
Real bug fixed:
- SynchronizationHandler::import — $idArrays = ['actions', 'followUps']
but the inner array_map() also checks for $arrayKey === 'conditions'.
The conditions slugs were never re-converted to IDs on import, even
though export() does serialise them. Brought $idArrays back in sync
with export() by adding 'conditions'.
Dead-branch comparisons removed:
- AuthorizationService::authorizeJwt: `$token === null` after substr()
(substr returns string on PHP 8+; only `=== ''` is reachable)
- AuthorizationService::checkUserGroups: `$user === false` after
IUserSession::getUser() (returns ?IUser, never bool; use === null)
- UserController::me: `$currentUser === false` (same issue)
- EndpointService line 441: `else if ($serializedObject === null)` on a
parameter typed `array` (never null; drop the dead branch — the third
else { …mapper->find… } branch already handles the !== [] case)
- EndpointService line 750: simplified `$ids === null || empty($ids)` to
just `empty($ids)`
- MappingService::executeMappingLocal:
+ `$cast === false` after explode() (which never returns false on PHP 8+)
+ Collapsed `if (is_array($output) === false) { if ($output === null)
{…} else {…} }` to a single defensive empty-array fallback with a
comment explaining the analysis-vs-safety tradeoff
Docblock relaxation (to widen overly-narrow array shape that masked
runtime int<->string key coercion):
- SynchronizationHandler::export / ::import — $mappings phpdoc relaxed
from `array<string,array{idToSlug:array<string,string>,
slugToId:array<string,string>}>` to `array<string,mixed>` with a
prose comment about the actual runtime shape. The narrow shape was
causing phpstan to declare `isset($mappings['mapping']['idToSlug'][
(int) $sourceTargetMapping])` to always be false (int key on
string-keyed array), even though PHP coerces those at runtime.
Baseline progress:
- Before: 83 entries (after #932)
- After: 72 entries (-11)
Verified:
- phpstan analyse → [OK] No errors with regenerated baseline
- phpcs lib/ → 0 errors
* feat(manifest): add 'View logs' header button on 5 index pages (#934)
Page-header level shortcut to the corresponding *Logs index, placed in
CnActionsBar's overflow menu next to the Add/Actions buttons.
Sources → SourceLogs
Endpoints → EndpointLogs
Jobs → JobLogs
Synchronizations → SynchronizationLogs (+ View contracts pulled up
from row-level since it makes sense at page-level)
CloudEvents → CloudEventLogs
Complements the row-level 'View logs' action shipped in #875 (which
pre-filters logs by the clicked parent row's id). The header-level
button takes the user to the UNFILTERED log list — useful when
browsing logs across all parents.
Uses the built-in CnIndexPage 'navigate' handler, no JS changes.
* fix(icons): restore pre-refactor TextBoxOutline for 'View logs' actions (#938)
The headerActions[] + row actions[] view-logs entries used 'icon-history'
(Nextcloud CSS class). CnActionsBar's icon resolver routes 'icon-*'
strings through a <span class=...> (Nextcloud built-in CSS icon set),
NOT through CnIcon — so the rendered glyph was a tiny back-curved
arrow that didn't size-match the surrounding NcLoadingIcon/Import/
Export/Refresh MDI icons (all 20px).
Plus: pre-refactor (chain-D and earlier) the Logs menu items used
TextBoxOutline (document with horizontal text lines) — semantically
right for log entries and visually consistent with the rest of the
nav. The 'icon-history' was an unrelated post-refactor regression.
Fix:
- Switch all 5 headerActions[] + 5 row actions[] view-logs entries
in src/manifest.json from 'icon-history' → 'TextBoxOutline'
- Import TextBoxOutline in src/main.js and pass it to registerIcons({})
so CnIcon's per-app registry can resolve it. Without registration
the fallback is HelpCircleOutline (the '?' placeholder).
Bundle size unchanged (one new icon imports ~1KB compressed).
* retrofit: annotate openconnector Bucket 1 (8 methods / 9 REQs) (#937)
* retrofit: annotate 8 methods across 2 files (Bucket 1)
Applied @spec tags pointing at ghost change
retrofit-2026-05-24-annotate-openconnector. No logic changes.
Source: openspec/coverage-report.md generated 2026-05-24.
Refs ConductionNL/openconnector#936
* retrofit: add annotation commit to blame-ignore-revs
* retrofit: archive ghost change retrofit-2026-05-24-annotate-openconnector
* feat(menu): restore pre-refactor MDI icons across the nav menu (#939)
Chain-E's manifest cutover replaced all menu icons with Nextcloud
`icon-*` CSS classes which lost semantic specificity (e.g. Sources got
`icon-link` — a generic chain — instead of the original
DatabaseArrowLeftOutline that conveys 'data flowing into the system'),
and several were outright wrong (Webhooks got `icon-mail` — an
envelope, semantically unrelated to HTTP callbacks).
Restored mapping (12 items):
Dashboard → Finance (was icon-category-dashboard)
Sources → DatabaseArrowLeftOutline (was icon-link)
Endpoints → Api (was icon-category-integration)
Consumers → AccountMultipleOutline (was icon-group)
Webhooks → Webhook (was icon-mail; mail icon was a regression)
Jobs → Update (was icon-category-workflow)
Mappings → SitemapOutline (was icon-category-customization)
Rules → ScaleBalance (was icon-toggle; pre-refactor was a SitemapOutline duplicate of Mappings, so swap to ScaleBalance for 'rule of judgment' semantics)
Synchronizations → VectorPolylinePlus (was icon-history)
Cloud events → CloudUploadOutline (was icon-category-monitoring)
Documentation → BookOpenVariant (was icon-info)
Settings → Cog (was icon-settings)
Plus: imported + registered all 13 new icons (12 menu + TextBoxOutline
from #938) in src/main.js so CnIcon's per-app registry can resolve them.
Bundle size delta: +12KB compressed (one vue-material-design-icons
component per icon, each ~1KB).
* retrofit: draft object-service-shim spec + annotate 9 methods (#940)
Reverse-specs the MongoDB Data API CRUD wrapper + OpenRegister bridge
that lives in lib/Service/ObjectService.php today. Cluster identified
by openspec/coverage-report.md (Bucket 2b) generated 2026-05-24.
- 5 REQs covering all 9 cluster methods
- design.md flags observed-but-suspicious behaviour (getClient discards
stripped config; getOpenRegisters swallows every Throwable; getMapper
null-deref when OR not installed) without silently fixing it
- Notes that openconnector-services-direct-or-usage will delete this
class; spec captures the pre-deletion baseline
Refs ConductionNL/openconnector#936
* retrofit: draft authorization-jwt spec + annotate 9 methods (#941)
Reverse-specs AuthorizationService.php — JWT/Basic/OAuth/API-key flows
plus CORS header injection. Cluster identified by
openspec/coverage-report.md (Bucket 2b) generated 2026-05-24.
- 5 REQs covering all 9 cluster methods (4 public schemes + CORS, plus
4 private JWT helpers folded under REQ-001)
- design.md flags observed-but-suspicious behaviour (HS512 missing from
the AlgorithmManager despite HMAC_ALGORITHMS const; commented-out
user/group allow-list in authorizeBasic/authorizeOAuth; public-key
file race in getJWK; unbounded Origin echo in CORS) without silently
fixing it
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft http-call-engine spec + annotate 15 methods (#942)
Reverse-specs CallService (11 methods, 831 LOC) + SOAPService
(4 methods, 305 LOC) — openconnector's outbound HTTP/SOAP dispatch
engine. Cluster identified by openspec/coverage-report.md (Bucket 2b)
generated 2026-05-24.
- 5 REQs covering all 15 cluster methods
- design.md flags observed-but-suspicious behaviour with severity
ratings (XXE-risk window in SOAPService::callSoapSource via the
permissive libxml_set_external_entity_loader is the highest;
microtime+pid filename collisions; secrets stripping via substring
match; hot-path OR write in sourceRateLimit; SSL keys in /var/tmp)
- Private helpers folded under public REQs they exclusively support
Refs ConductionNL/openconnector#936
* retrofit: draft events-cloudevents spec + annotate 17 methods (#943)
Reverse-specs EventService + EventsController — openconnector's
CloudEvents producer + delivery pipeline (OR object lifecycle →
event_subscription fan-out → push/pull message delivery). Cluster
identified by openspec/coverage-report.md (Bucket 2b) generated
2026-05-24.
- 5 REQs covering all 17 cluster methods (10 service + 7 controller)
- design.md flags multiple security issues with severity ratings:
* REQ-005: every controller endpoint is NoAdminRequired with no
per-object IDOR guard (matches hydra-gate-no-admin-idor pattern)
* REQ-005: NoCSRFRequired on state-changing endpoints
(subscribe/updateSubscription/unsubscribe)
* REQ-001: ExpressionLanguage::evaluate runs caller-supplied
expression strings against event payload
* REQ-002: processRetries never increments retryCount — pending
messages re-attempt forever (functional bug)
- Notes section per REQ surfaces these without silently fixing
Refs ConductionNL/openconnector#936
* retrofit: draft authentication-twig spec + annotate 22 methods (#944)
Reverse-specs AuthenticationService (10) + AuthenticationRuntime (3)
+ MappingRuntime (9) — openconnector's outbound auth + Twig template
helpers. Cluster identified by openspec/coverage-report.md (Bucket 2b)
generated 2026-05-24.
- 5 REQs covering all 22 cluster methods
- design.md flags high-severity findings:
* REQ-002: private key written to /var/tmp/privatekey-<microtime+pid>
then unlink — leak window (same pattern as authorization-jwt#REQ-001)
* REQ-002: generateJWT catches its own Exception and returns the
error message AS the JWT string — silently broken tokens propagate
to upstream auth headers (functional bug)
* REQ-002: getHSJWK addslashes-then-base64 produces peer-mismatched
symmetric keys on quote/backslash/NUL chars
* REQ-002: getJWTPayload Twig-renders payload before JSON-decoding
(template injection if payload is caller-controlled)
Refs ConductionNL/openconnector#936
* retrofit: draft repair-and-app-boot spec + annotate 2 methods (#945)
Reverse-specs the openconnector install/boot bootstrap path —
InitializeRegister repair step + Application::registerIntegrationProviders.
Cluster identified by openspec/coverage-report.md (Bucket 2b) generated
2026-05-24.
- 2 REQs covering 2 cluster methods (1:1 — both methods are
single-purpose lifecycle hooks)
- design.md flags observed-but-suspicious behaviour: repair step
swallows every Throwable so install can appear green when the
register import actually failed; class docblock advertises a
storage_migrated guard that run() never reads
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft storage-uploads spec + annotate 4 methods (#946)
Reverse-specs StorageService.php — multi-part upload reconciliation
plus single-shot writeFile helper. Cluster identified by
openspec/coverage-report.md (Bucket 2b) generated 2026-05-24.
- 4 REQs covering all 4 cluster methods (createUpload / writeFile /
writePart / attemptCloseUpload — 1:1 because each carries a distinct
observable contract)
- design.md + REQ-002 Notes flag observed bug: writeFile() references
$this->userSession which is never declared or injected — method is
effectively dead code at runtime
- Memory-profile callout: attemptCloseUpload concatenates the full
upload in PHP memory, imports streaming interfaces it never uses
- IDOR surface: writePart authorises on UUID knowledge only
Refs ConductionNL/openconnector#936
* retrofit: draft organisation-bridge spec + annotate 6 methods (#947)
Reverse-specs OrganisationBridgeService.php — soft-fail adapter to OR's
OrganisationService. Cluster identified by openspec/coverage-report.md
(Bucket 2b) generated 2026-05-24.
- 5 REQs covering all 6 cluster methods (getActiveOrganisation +
getUserOrganisations folded under REQ-005 — same shape, same fail-paths)
- design.md flags the unsafe-auth-resolver pattern (getOrganisationService
catch->null) that triggers hydra-gate-unsafe-auth-resolver: OWASP
A01:2021 / CWE-863 silent-fail-open shape; consumers cannot distinguish
'OR briefly unavailable' from 'user has no org affiliation'
- Additional findings flagged: exception-message leak in
setActiveOrganisation; data-quality collapse where unavailable / no-data
/ errored all return the same falsy shape
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft xml-response spec + annotate 7 methods (#948)
Reverse-specs XMLResponse.php — DOMDocument-backed XML emission with
@root / @attributes / #text conventions plus a render-callback escape
hatch. Cluster identified by openspec/coverage-report.md (Bucket 2b)
generated 2026-05-24.
- 5 REQs covering all 7 cluster methods (getData + setRenderCallback
paired under REQ-001, createChildElement + createSafeTextNode paired
under REQ-005)
- design.md flags observed-but-suspicious behaviour:
* createSafeTextNode double-decodes HTML entities — name oversells
safety; downstream HTML consumers could see unwrapped markup
* setRenderCallback is an unbounded bypass of every safety property
* IQueryBuilder special-case suggests prior incident with SQL leak
* partial sanitisation: numeric tag names rewritten but XML-invalid
characters (`:` / `<` / spaces) trigger DOMException
- No inbound XML parsing here, so no XXE/billion-laughs surface
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft job-scheduling spec + annotate 13 methods (#949)
Reverse-specs the openconnector job-scheduling subsystem — JobsController
(HTTP) + JobTask / LogCleanUpTask (cron) + JobService (business logic).
Cluster identified by openspec/coverage-report.md (Bucket 2b) generated
2026-05-24.
- 5 REQs covering all 13 cluster methods (REQ-002 pairs run+test,
REQ-004 folds 7 JobService methods under one observable
schedule+execute+log capability, REQ-005 pairs LogCleanUpTask.run +
cleanupSchema)
- design.md + REQ Notes flag MULTIPLE high-severity surfaces:
* HIGH (IDOR / ADR-005 Rule 3 / OWASP A01:2021) — JobsController
run+test are @NoAdminRequired + @NoCSRFRequired with no per-object
guard; any authed user triggers arbitrary job execution
* HIGH (privilege escalation) — executeJob sets the session user
but never resets, chaining the IDOR into admin impersonation
* HIGH (disable doesn't disable) — scheduleJob's disable path
leaves the NC oc_jobs row in place (removeById commented out)
* MEDIUM (cron-storm) — LogCleanUpTask runs every minute with
setAllowParallelRuns(true)
* MEDIUM (silently broken filter) — JobsController::logs builds
date_from/date_to/status filter arrays then never uses them
* MEDIUM (correctness) — JobTask::run ignores its \$argument and
always sweeps all due jobs, regardless of which one was queued
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft logs-and-statistics spec + annotate 14 methods (#950)
Reverse-specs the openconnector logs / stats / retention surface —
LogsController, SourcesController (logs+test), SettingsController
(rebase), SettingsService. Cluster identified by
openspec/coverage-report.md (Bucket 2b) generated 2026-05-24.
- 5 REQs covering all 14 cluster methods (paired related methods
to stay under cap: REQ-001 logs CRUD, REQ-002 stats+export,
REQ-003 sources logs+test, REQ-004 settings read+update,
REQ-005 stats+rebase+helpers+http-wrapper)
- design.md flags MULTIPLE HIGH-severity authorization findings:
* HIGH IDOR — LogsController index/show/destroy/statistics/export,
SourcesController logs+test, SettingsController rebase ALL
@NoAdminRequired + @NoCSRFRequired with no per-object guards
* HIGH SSRF — SourcesController::test triggers arbitrary outbound
HTTP via any authed user with a guessable source UUID
* HIGH privilege escalation / data destruction — chaining
SettingsService::updateSettings (retention=1ms) + rebase
effectively wipes every log on the instance, both @NoAdminRequired
* HIGH audit-trail tampering — LogsController::destroy lets users
delete logs of their own activity
* MEDIUM observable bug — SettingsService::rebase' first 2 UPDATE
branches both target call_logs and clobber callLogsUpdated
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft flow-token-helper spec + annotate 21 methods (#951)
Reverse-specs FlowToken.php — mutable value-bag threaded through the
rule pipeline carrying paired original/amended snapshots of request /
response / sync-input / sync-output. Cluster identified by
openspec/coverage-report.md (Bucket 2b) generated 2026-05-24.
- 5 REQs covering all 21 cluster methods (paired methods folded:
REQ-001 request ingest + 4 private helpers, REQ-002 response
ingest, REQ-003 sync I/O originals pair, REQ-004 all 8 amended
pass-through accessors, REQ-005 __serialize)
- design.md + REQ-001 Notes flag HIGH-severity XXE:
parseContent + looksLikeXml call simplexml_load_string with PHP's
default libxml options — DTD entity resolution including external
entities is enabled. Any caller that hits an endpoint whose rule
pipeline reads the FlowToken request can trigger XXE file-read,
SSRF, or billion-laughs DoS. CWE-611 / OWASP A05:2021.
- Additional findings flagged:
* MEDIUM proxy-header trust drift — constructor passes
proxyHeaders:true so downstream rules see client-controllable
X-Forwarded-* / X-Real-IP / X-Original-URI
* MEDIUM soft-DoS via multipart — every uploaded file is read
into memory with no size guard
* LOW edge-case priority — json_decode('false', true) !== null
so a literal 'false' body falls through to the XML branch
* LOW coupling surprise — re-setting Original does NOT re-seed
Amended (only the constructor does)
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: draft software-catalogus-events spec + annotate 23 methods (#952)
Reverse-specs SoftwareCatalogueService + SoftwareCatalogEventListener
— two concerns wired in one cluster: ArchiMate model graph extension
(extendModel/View/Node/Connection + find* helpers) and OR lifecycle
event provisioning (EventListener + handleNew*/handleContact*
orchestrators + 9 stub helpers). Cluster identified by
openspec/coverage-report.md (Bucket 2b) generated 2026-05-24.
- 5 REQs covering all 23 cluster methods (REQ-001 extend pipeline
entry points, REQ-002 node+connection extension pair, REQ-003 lookup
helpers trio, REQ-004 event-dispatch + 4 orchestrators folded, REQ-005
all 9 stub helpers folded — they share an identical log-only no-op
shape)
- design.md flags MAJOR findings:
* HIGH stub-scan (hydra-gate-stub-scan) — 9 of 23 methods are TODO:
Implement bodies with only a logger->info line. The pipeline LOOKS
green at every layer (EventListener catches errors, orchestrators
dispatch, helpers log every call) but NO email is sent, NO NC
group is created, NO user is provisioned.
* HIGH observable bug — extendModel's inner array_map callback
never returns the extendView call; \$promises is a list of nulls;
all(\$promises) resolves immediately. The aggregation is broken.
* MEDIUM silent-failure — \$deferred->promise()->catch(fn () {})
in extendModel/extendView swallows any rejection at the boundary
* MEDIUM gap — EventListener has no organisation update / delete
handler; renaming or removing orgs triggers no NC-side reaction
* MEDIUM last-write-wins — extendView saveObject has no
conflict-detection; parallel extension runs race
- Notes section per REQ surfaces ambiguity for follow-up changes
Refs ConductionNL/openconnector#936
* retrofit: reverse-spec mapping-and-search (5 REQs / 24 methods) (#953)
* retrofit: draft mapping-and-search spec + annotate 24 methods
Reverse-spec ghost change retrofit-2026-05-25-mapping-and-search:
5 REQs describing the observed behavior of MappingService,
MappingsController, and SearchService. Annotates 24 methods with
@spec tags. No behavior changes.
Notes flag: SearchService::search() references constructor-undefined
properties (runtime fatal on the federated path); mapping endpoints
are @NoAdminRequired; the 'date' cast is a PHP date() passthrough.
* retrofit: archive mapping-and-search change
Merges the delta spec into openspec/specs/mapping-and-search/spec.md
(new capability, retrofit: true).
* chore: add mapping-and-search annotation commit to git-blame-ignore-revs
* retrofit: reverse-spec rule-pipeline (5 REQs / 31 methods) (#954)
* retrofit: draft rule-pipeline spec + annotate 31 methods
Reverse-spec the endpoint rule engine (Bucket 2b cluster `rule-pipeline`,
no capability owner) as 5 REQs describing observed behaviour. Code already
exists — this retroactively specifies it per ADR-002 (rule engine is
openconnector-local, no OR equivalent).
Annotates 31 methods across EndpointService.php (rule-evaluation) and
RuleService.php (custom catalogue + external-extension) with
@spec tasks.md#task-N tags. No code behaviour changes.
Notes flag observed-but-suspicious behaviour for follow-up: silent per-file
write failure (processWriteFileRule), no-op JS rule stub (processJavaScriptRule),
auth rule does not propagate principal, extendExternalUrl auto-creates Source
rows, software-catalogus rule carries hard-coded test data.
Source: openspec/coverage-report.md (2026-05-24). Refs ConductionNL/openconnector#936
* retrofit: archive rule-pipeline change (merge spec into main specs)
Lands the rule-pipeline capability spec at openspec/specs/rule-pipeline/spec.md
(retrofit: true). Records the annotation commit in .git-blame-ignore-revs.
Refs ConductionNL/openconnector#936
* retrofit: reverse-spec configuration-export-import (5 REQs / 35 methods) (#955)
* retrofit: draft configuration-export-import spec + annotate 35 methods
Reverse-engineers a spec from the observed behaviour of the
configuration-export-import cluster (ConfigurationService + six
ConfigurationHandlers) as 5 numbered REQs under a new
configuration-export-import capability. Code already exists; this change
retroactively specifies it and tags each of the 35 methods with an @spec
pointer.
Notes flag the security-relevant and fragile observed behaviour without
changing it: slug-not-found verbatim fallback (dangling FK), single-pass
import map, no per-entity payload validation on import, and that
SourceHandler credential redaction is the only barrier given plaintext
storage (ADR-007) with substring-based header matching that can miss
non-standard credential header names.
* retrofit: archive configuration-export-import change
Merges the configuration-export-import delta into
openspec/specs/configuration-export-import/spec.md (5 REQs, retrofit: true)
and moves the ghost change into openspec/changes/archive/. Restores the
retrofit frontmatter and capability Purpose the archive scaffold dropped.
* chore: ignore reverse-spec annotation commit in git blame
* retrofit: reverse-spec user-management-and-login (5 REQs / 31 methods) (#956)
* retrofit: draft user-management-and-login spec + annotate 31 methods
Reverse-spec ghost change retrofit-2026-05-25-user-management-and-login:
5 REQs describing the observed behavior of UserController, UserService,
and SecurityService (self-profile, custom login with brute-force
protection, logout, CORS, input sanitisation + security headers).
Annotates 31 methods with @spec tags. No behavior changes.
Notes flag: reflected-Origin CORS with Access-Control-Allow-Credentials:
true; me() inline Basic auth vs route auth posture;
getUsedSpaceMemorySafe() overwrite ordering; 'succesful_login' typo.
* retrofit: archive user-management-and-login change
Merges the delta spec into openspec/specs/user-management-and-login/spec.md
(new capability, retrofit: true).
* chore: add user-management-and-login annotation commit to git-blame-ignore-revs
* retrofit: reverse-spec endpoint-runtime (5 REQs / 32 methods) (#957)
* retrofit: draft endpoint-runtime spec + annotate 32 methods
Reverse-spec the endpoint dispatch/caching/normalisation layer (Bucket 2b
cluster `endpoint-runtime`, no capability owner) as 5 REQs describing observed
behaviour. Code already exists — this retroactively specifies it. Target
dispatch follows ADR-008 (polymorphic targetType/targetId).
Annotates 32 methods across EndpointsController.php (dispatch + simple fast
path), EndpointService.php (full pipeline + target dispatch + request/response
normalisation), and EndpointCacheService.php (endpoint resolution cache) with
@spec tasks.md#task-N tags. No code behaviour changes.
Notes flag observed-but-suspicious behaviour for follow-up: handleRequest
returns the full exception stack trace in the HTTP 400 body (information
disclosure, OWASP A05:2021), getRuleById silently drops unresolvable rule ids,
logs() returns an empty placeholder result pending call-log wiring.
Source: o…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Canonicalize
appinfo/info.xmlper the fleet drift research innextcloud-app-template/docs/fleet-drift-deeper.md §4.
<php min-version="8.3">— matchescomposer require.php(^8.3fleet-wide)<nextcloud min-version="28" max-version="34">— converge on the fleet's standard support range<licence>agpl</licence>— fix the casing/value drift (the fleet had 4 spellings:agpl,eupl,EUPL-1.2,AGPL-3.0-or-later)Per-app fields (
<id>,<name>,<description>,<version>,<screenshot>, etc.) are preserved.Licence note
We stay on the
agplworkaround per the EUPL store-listing pattern. The upstream fix landed in nextcloud/server#60212 (NC 34) and nextcloud/appstore#1754. Switch toEUPL-1.2once the fleet floor reaches NC 34 — tracked in ConductionNL/.github#98.How this PR was made
Opened by
hydra/scripts/fleet-sync/info-xml.sh— seethe script
for the canonical values + rewrite logic. The script reads
appinfo/info.xml, surgically rewrites the three canonical fields,and opens a PR per fleet app.