fix(events): resolve the schema slug listeners compare against — 3 procest listeners had never run - #692
Merged
Conversation
OpenRegister object events carry the schema as an ID, never a slug. ObjectEntity::jsonSerialize() builds @self from getObjectArray(), which sets 'schema' => $this->schema, and SaveObject writes that field as setSchema((string) $schemaId). There is no 'schemaSlug' key on @self and there never has been. Three listeners resolved the slug by reading @self.schemaSlug (absent), then @self.schema (an id), so their strict slug comparisons could not match and their handler bodies had never executed once — no exception, no log, no failing test. Adds ObjectSchemaSlugResolver: one shared, request-memoised id->slug lookup through OpenRegister's SchemaMapper (which caches per request), resolved via the container so procest still boots without OpenRegister. An unresolvable schema yields '', which matches no literal, so the previous fail-closed behaviour is preserved. The other ten procest listeners are NOT affected: they compare @self.schema against a *_schema app-config value, and SettingsService stores those as ids, so they were already comparing id against id.
rubenvdlinde
requested review from
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
August 1, 2026 09:05
This was referenced Aug 1, 2026
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.
Fixes part of #690 (the issue is filed on procest but the bug class is fleet-wide).
The bug
OpenRegister object events carry the schema as an id, never a slug:
ObjectEntity::jsonSerialize()builds@selffromgetObjectArray(), which sets'schema' => $this->schema(lib/Db/ObjectEntity.php:947).$this->schemais written bySaveObjectassetSchema((string) $schemaId).schemaSlugkey on@self, and there never has been.Three procest listeners resolved the slug by reading
@self.schemaSlug(absent), then falling back to@self.schema(an id). Their strict slug comparisons therefore could never match, and their handler bodies had never executed once. Green-but-dead: no exception, no log, no failing test — while still paying full DI construction and invocation on every matching object write.BezwaarLifecycleListenerin_array($slug, ['bezwaar','objection','hearingSession','advisoryReport','decision'], true)BezwaarLegalHoldListenerin_array($slug, ['objection','bezwaar'] / ['bezwaarDecision','appealDecision'], true)TermijnCaseCreatedListener$slug !== 'case'TermijnInstancehas ever been auto-bound — termijn/dwangsom tracking never startedThe fix
One shared
ObjectSchemaSlugResolver(lib/Service/ObjectSchemaSlugResolver.php) rather than three per-listener variants. It turns the id the payload actually carries into the slug the handlers are written against, via OpenRegister'sSchemaMapper::find()(which keeps a request-scoped cache), memoising hits and misses for the request.OpenRegister is reached through the container rather than constructor-injected, matching
SettingsService— procest carries no hard dependency on OpenRegister and still boots without it. An unresolvable schema yields'', which matches no literal, so the previous fail-closed behaviour is preserved.What is NOT affected
The other ten procest listeners are fine and are deliberately untouched. They compare
@self.schemaagainst a*_schemaapp-config value, andSettingsService::reconcileSingleSchemaKey()resolves slug→id and stores(string) $schema->getId()— so they were already comparing id against id. Verified live:occ config:app:get procest bezwaar_schema→116, which is exactly what@self.schemacarries.A naive "count the slug comparisons" scan reports 12 dead listeners in procest. The real number is 3.
Positive control (live, on the shared dev instance)
A negative-only control passes against a listener that does nothing at all, so both directions were measured on a real API write of a
bezwaarobject (registerprocest=17, schemabezwaar=116).Before — proxy invoked the listener, body did nothing:
The same request wrote 270
"level":0(debug) entries, so the log channel itself was demonstrably working — the zero is the listener, not the instrument.After — same write, same instance:
"schema":"bezwaar"is the resolver turning id116into the slug. The independent corroboration islistenerUs118 us -> 4624 us: the handlers are now doing real work instead of returning at the first guard. Host load average at measurement: 32.47.Behaviour change — read before merging
These listeners have never run. Waking them is a behaviour change, not a no-op.
BezwaarLifecycleListener— safe. Its body is a singlelogger->debug(); no side effects.TermijnCaseCreatedListener— writes objects. Creates an AWBTermijnInstanceper new case. Correct intended behaviour, but it will start writing on every case create.BezwaarLegalHoldListener— writes OpenRegister legal holds. Suppresses retention/destruction for cases under an Awb procedure. Correct and compliance-relevant, but it changes archival behaviour.Honest limits of this verification
BezwaarLifecycleListener: verified end-to-end.BezwaarLegalHoldListener: comparison fixed, end-to-end UNVERIFIED. After the fix abezwaarcreate still produced no legal hold on the linked case. The guard is proven fixed (same resolver, same payload, same request as the verified listener), butapplyHold()returns silently whenresolveOr()orresolveCaseObject()yields null —resolveCaseObject()callsObjectEntityMapper::findByUuid()with an unscoped UUID. That looks like a second, independent defect downstream of the guard and is deliberately not fixed here.TermijnCaseCreatedListener: comparison fixed, end-to-end UNVERIFIED. Acasecreate (schema 92, slugcase) produced neither aTermijnInstancenor thecatchbranch's debug line, so something downstream of the guard is also not working. Not chased here.hearingSessionandadvisoryReport(BezwaarLifecycleListener) andbezwaarDecision/appealDecision(BezwaarLegalHoldListener) are camelCase; OpenRegister slugs are lower-kebab (assessment-result,grade-entry). None of these four schemas exists on the dev instance, so the comparison cannot be exercised and the literals were left alone rather than guessed at. These branches remain dead after this PR.phpcsis clean on all four files. The three remaining warnings are the pre-existing "missing@specPHPDoc tag" class-level warnings; no@specvalue was invented for them.