Ga naar hoofdinhoud

Codebase Audit — 2026-05-01

Sweep for three repeating defect patterns surfaced during the platform-integration-2026-04 mega-PR review.

  • Branch base: platform-integration-2026-04 (HEAD 22c5625ef)
  • Already fixed today: file-actions (10 routes), workflow-operations (16 routes via PR #1390), FileLockHandler distributed-cache persistence (22c5625ef).

Pattern A — Unrouted controller methods

Public controller methods returning a JSONResponse / StreamResponse / DataDownloadResponse / TemplateResponse whose route is missing from appinfo/routes.php.

Method: scanned 85 controllers, 524 response-returning public methods. Cross-checked each against appinfo/routes.php (468 route entries). Resource auto-routes (registers, schemas, sources, configurations, applications, agents, endpoints, mappings, consumers) excluded — those auto-generate index/show/create/update/destroy.

Result: 67 unrouted methods across 17 controllers.

Tier 1 — Real route gaps (live spec features, no route)

These map 1:1 to spec items marked [x] and are functional handlers, not dead code. Recommend fixing.

Controller / methodExpected routeSpec
UserController::changePassworduser#changePasswordprofile-actions (archived 2026-05-01)
UserController::uploadAvataruser#uploadAvatarprofile-actions
UserController::deleteAvataruser#deleteAvatarprofile-actions
UserController::exportDatauser#exportDataprofile-actions
UserController::getNotificationPreferencesuser#getNotificationPreferencesprofile-actions
UserController::updateNotificationPreferencesuser#updateNotificationPreferencesprofile-actions
UserController::getActivityuser#getActivityprofile-actions
UserController::listTokensuser#listTokensprofile-actions
UserController::createTokenuser#createTokenprofile-actions
UserController::revokeTokenuser#revokeTokenprofile-actions
UserController::requestDeactivationuser#requestDeactivationprofile-actions
UserController::getDeactivationStatususer#getDeactivationStatusprofile-actions
UserController::cancelDeactivationuser#cancelDeactivationprofile-actions
CalendarEventsController::indexcalendarEvents#indexnextcloud-entity-relations
CalendarEventsController::createcalendarEvents#createnextcloud-entity-relations
CalendarEventsController::linkcalendarEvents#linknextcloud-entity-relations
CalendarEventsController::destroycalendarEvents#destroynextcloud-entity-relations
DeckController::indexdeck#indexnextcloud-entity-relations
DeckController::createdeck#createnextcloud-entity-relations
DeckController::destroydeck#destroynextcloud-entity-relations
DeckController::objectsdeck#objectsnextcloud-entity-relations
ContactsController::indexcontacts#indexnextcloud-entity-relations
ContactsController::createcontacts#createnextcloud-entity-relations
ContactsController::updatecontacts#updatenextcloud-entity-relations
ContactsController::destroycontacts#destroynextcloud-entity-relations
ContactsController::objectscontacts#objectsnextcloud-entity-relations
RelationsController::indexrelations#indexnextcloud-entity-relations
LinkedEntityController::addObjectLinklinkedEntity#addObjectLinklinked-entity-types
LinkedEntityController::removeObjectLinklinkedEntity#removeObjectLinklinked-entity-types
LinkedEntityController::addRegisterLinklinkedEntity#addRegisterLinklinked-entity-types
LinkedEntityController::addSchemaLinklinkedEntity#addSchemaLinklinked-entity-types
LinkedEntityController::reverseLookuplinkedEntity#reverseLookuplinked-entity-types
TmloController::summarytmlo#summarytmlo-metadata
TmloController::exportSingletmlo#exportSingle (Response)tmlo-metadata
TmloController::exportBatchtmlo#exportBatch (Response)tmlo-metadata
TagsController::indextags#index(no spec — pre-existing)
TagsController::addtags#add(no spec — pre-existing)
TagsController::removetags#remove(no spec — pre-existing)
NotesController::updatenotes#update(no spec — pre-existing)
ConfigurationsController::exportconfigurations#export(configuration#export exists; this is an alternate handler on the plural controller)
FileSidebarController::getObjectsForFilefileSidebar#getObjectsForFile(no spec found)
FileSidebarController::getExtractionStatusfileSidebar#getExtractionStatus(no spec found)
Settings\CacheSettings::clearSpecificCollectionSettings\CacheSettings#clearSpecificCollectionsettings refactor
Settings\ConfigurationSettings::getObjectCollectionFieldsSettings\ConfigurationSettings#getObjectCollectionFieldssettings refactor
Settings\ConfigurationSettings::createMissingObjectFieldsSettings\ConfigurationSettings#createMissingObjectFieldssettings refactor
Settings\FileSettings::getFileCollectionFieldsSettings\FileSettings#getFileCollectionFieldssettings refactor
Settings\FileSettings::createMissingFileFieldsSettings\FileSettings#createMissingFileFieldssettings refactor
Settings\LlmSettings::getVectorStatsSettings\LlmSettings#getVectorStatssettings refactor
Settings\SolrManagement::listSolrCollectionsSettings\SolrManagement#listSolrCollectionssettings refactor
Settings\SolrManagement::listSolrConfigSetsSettings\SolrManagement#listSolrConfigSetssettings refactor
Settings\SolrManagement::createSolrConfigSetSettings\SolrManagement#createSolrConfigSetsettings refactor
Settings\SolrManagement::deleteSolrConfigSetSettings\SolrManagement#deleteSolrConfigSetsettings refactor
Settings\SolrManagement::createSolrCollectionSettings\SolrManagement#createSolrCollectionsettings refactor
Settings\SolrManagement::copySolrCollectionSettings\SolrManagement#copySolrCollectionsettings refactor

Tier 2 — Dead duplicate methods (superseded, safe to delete)

These exist on legacy controllers but the work moved to namespaced sub-controllers. Routes already point to the new homes. The legacy methods are dead code.

Legacy method (no route)Live replacement
SettingsController::testSchemaMappingSettings\SolrOperations#testSchemaMapping
SettingsController::testSetupHandlerSettings\SolrOperations#testSetupHandler
SettingsController::reindexSpecificCollectionSettings\SolrManagement#reindexSpecificCollection
SettingsController::semanticSearchsolr#semanticSearch
SettingsController::hybridSearchsolr#hybridSearch
SettingsController::load(no replacement — legacy load endpoint, dead since refactor)
SettingsController::updatePublishingOptions(no replacement — legacy publishing config endpoint)
ConfigurationController::index / show / create / update / destroyConfigurationsController (plural, registered as resources entry)
ChatController::page(no replacement — chat#page template render legacy)
ObjectsController::logsauditTrail#objects (same URL)
RegistersController::objectsobjects#index (registered)

Pattern A verdict

Needs design call. 67 instances — far above the 3-instance inline-fix threshold. The nextcloud-entity-relations cluster (calendar/deck/contacts/relations) and the profile-actions cluster (13 user endpoints) are the largest gaps and ship in a single PR per spec, not in this audit. Tier 2 dead duplicates should be removed in a separate cleanup pass.


Pattern B — In-memory state that should be persistent

Scanned 68 private array $... declarations across lib/Service/ and lib/Db/.

Result: Zero unfixed SUSPECT instances.

Class::$propertyVerdictNote
FileLockHandler::$localFallbackOKAlready fixed in 22c5625ef; ICacheFactory backed, in-memory is documented test-only fallback.
RateLimiter (uses ?ICache $cache, no private array)OKICacheFactory-backed token buckets.
LockHandler (object locks)OKPersists via magicMapper → DB.
LifecycleGuardRegistry::$cacheOKPer-request DI resolution cache; documented.
RequestScopedCache::$cacheOKClass name says it.
SchemaMapper::$findCache, RegisterMapper::$findCacheOKPer-request memoisation; mappers are request-scoped.
SetupHandler::$infrastructureCreated, $setupProgressOKPer-setup-call run state, returned in response envelope.
RenderObject::$registersCache, $schemasCache, $objectsCache, $ultraPreloadCache, $inverseRelationCacheOKPer-request render aggregation cache; documented.
SaveObject::$createdSubObjects, $schemaCache, $registerCache, $schemaReferenceCache, $referenceValidationCacheOKPer-save-tx state.
PermissionHandler::$cachedRegisterAuth, $cachedRegisterConfig, $permissionCacheOKPer-request permission cache.
CacheHandler::$objectCache, $nameCache, $inMemoryQueryCache, $statsOKDocumented per-request fast-lookup.
MagicFacetHandler::$facetCache, $uuidLabelCache, $fieldLabelCache, $warmedFields, $cacheStats, $columnCacheOKPer-request facet aggregation.
MappingService::$templateCacheOKTwig template instance cache.
OasService::$oas, LanguageService::$acceptedLanguages, ToolRegistry::$tools, VectorizationService::$strategiesOKBoot-time DI / strategy registry.
ImportHandler::$registersMap, $schemasMap, ExportHandler::$registersMap, $schemasMapOKPer-import/export run state.
ImportService::$schemaPropertiesCacheOKPer-import memoisation.
Configuration*.bak2 filesSkippedBackup files, not loaded by autoloader.
All GraphQL/ private arraysOKPer-query schema generation cache.

Pattern B verdict

Clean. No new persistence-layer gaps beyond the already-fixed FileLockHandler. Every private array reviewed is either explicitly per-request (cache, render, save tx, query) or already backed by a persistent store at the class level (?ICache, MagicMapper, etc.).


Pattern C — Phantom ticks in tasks.md

Sampled 3 open changes from openspec/changes/: data-import-export, nextcloud-entity-relations, notificatie-engine. Sampled 5 ticked items per spec.

Spec: data-import-export (15 sampled)

ClaimReality
[x] importFromExcel at lib/Service/ImportService.php:265Exists at line 267 (off-by-2, fine)
[x] importFromCsv at line 349Exists at line 353 (off-by-4)
[x] resolveUuidNameMap at line 397+Exists at line 479 (line drift)
[x] RegistersController::importTemplate route presentRoute at appinfo/routes.php:411
[x] ImportServiceIntegrationTest 30 tests30 public function test* methods present

Verdict: clean.

Spec: nextcloud-entity-relations (5 sampled)

ClaimReality
[x] Add email routes to routes.php5 route entries present
[x] Add calendar event routes to routes.phpphantom — zero calendarEvents# routes in routes.php
[x] Add contact routes to routes.phpphantom — only contacts#match present; index/create/update/destroy/objects unrouted
[x] Add deck routes to routes.phpphantom — zero deck# routes in routes.php
[x] Implement RelationsController with unified endpointClass exists, but relations#index is unrouted — endpoint unreachable

Verdict: 4 phantom ticks. The line "44/53 backend tasks shipped" in the status block overstates progress.

Spec: notificatie-engine (5 sampled)

ClaimReality
[x] AnnotationNotificationDispatcher::emitNotificationMethod at line 757
[x] resolveLocalizedSubject methodMethod at line 639
[x] AnnotationNotificationDispatcherTest 14 tests14 test* methods present
[x] NotificationAnnotationValidatorTest 26 tests27 present (1 extra is fine)
[x] RateLimiterTest 8 tests covering token-bucket8 test* methods present

Verdict: clean.

Pattern C verdict

Mixed. 2 of 3 sampled specs clean; nextcloud-entity-relations has 4 phantom route ticks that map directly to the Tier 1 route gaps in Pattern A. Recommend retroactively unticking those items and folding them into a per-spec route-completion PR (calendar/contacts/deck/relations). Coincidentally the same gap surfaces in tmlo-metadata (archived but with phantom route tick on tasks 4.3 + 5.2) — those should be re-opened.


Summary

PatternInstancesVerdict
A — unrouted controller methods67 (Tier 1: 55 real gaps; Tier 2: 12 dead duplicates)needs design call
B — in-memory state0 unfixedclean
C — phantom ticks (sampled)4 in nextcloud-entity-relations, 2 in archived tmlo-metadataneeds design call

Patterns A and C overlap heavily — each phantom route tick corresponds to a Pattern A gap. A coordinated PR per affected spec (profile-actions, nextcloud-entity-relations cluster, tmlo-metadata, linked-entity-types) is the natural unit of work; this audit is the catalogue.