fix(platform-core): never silently drop a perspective with an unknown groupId (#6646) - #6648
Merged
Merged
Conversation
… groupId (#6646) The perspective aggregator places contributions into navigation groups by `groupId`. A groupId matching no declared group was discarded outright, so the group rendered empty - indistinguishable from "no module contributes here", with no console warning and no server log line anywhere. The "My Work" -> "Personal" rename triggered exactly that: every already-generated module still declared `groupId: 'my'`, and its personal pages vanished while the extension layer reported perfect health. Three layers, so the failure mode is removed rather than reported: - `perspectives.mjs` logs the unmatched groupId (naming the perspective and the id), then places the perspective in the extension point's DEFAULT group - one declaring `isDefault: true` - else the platform's `undefined-group` catch-all, else hands it back standalone for the consuming shell. It is never dropped. `undefined-group` is deliberately not marked default, so a no-groupId perspective on `application-perspectives` keeps its current standalone handling. - the single-group shells (personal / partner / admin) declare their one group `isDefault: true` and their generated perspectives no longer bake in its id. Repeating a platform constant in every generated artifact is a coupling that can only drift, and a rename of it could only be repaired by regenerating every module. - the four Harmonia shells' catch-all now rescues any `kind`, not just PRIMARY, and warns when it does. Enumerating kinds is how `PERSONAL` fell off the end of that chain despite a branch written to prevent silent drops. Tests: `PerspectiveGroupAggregationIT` covers both placements (verified to fail on the pre-fix aggregator); `IntentEmissionCoverageIT` now asserts each shell perspective is INSIDE its group rather than merely present in the response, and that no group id is baked into the generated artifact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 #6646.
Problem
The perspective aggregator (
platform-core/extension-services/modules/perspectives.mjs) places contributions into navigation groups bygroupId. AgroupIdmatching no declared group was discarded outright — so the group rendered empty, which is indistinguishable from "no module contributes here". No console warning, no server log line, nothing missing-looking in the UI.The
my→personalshell rename (#6611/#6612) triggered exactly that: already-generated modules still declaredgroupId: 'my', and their personal pages vanished while every intermediate layer reported success (/services/core/extensionslisted all of them witherror=''; the aggregator answeredGROUP id='personal' items=[]). The first hypotheses were all wrong — impersonation, identity mapping, roles, modules not bundled — because nothing anywhere said a perspective had been dropped.Fix
Three layers, so the failure mode is removed rather than merely reported.
1. The aggregator never drops, and never stays quiet. An unmatched
groupIdisconsole.error-ed naming the perspective and the id (it reaches the platform log — the cheap half, worth having on its own), then the perspective is placed in:isDefault: true, new;undefined-groupcatch-all (unchanged behaviour);undefined-groupis deliberately not marked default, so a no-groupIdperspective onapplication-perspectiveskeeps its current standalone handling and the shells' "Other" section.2. The single-group shells own the placement.
resources-personal/resources-partner/resources-admineach declare their one navigation groupisDefault: true, and their generated perspectives (ui/{my,partner,admin}/*-perspective.js.template) no longer bake in the group id at all. This is request 3 of the issue: repeating a platform constant in every generated artifact is a coupling that can only ever drift, and a rename could then only be repaired by regenerating every module. A stalegroupId: 'my'in an app built from already-published packages now lands inpersonaltoo — the fix reaches artifacts it cannot change.3. The shells' catch-all covers every kind. The four Harmonia shells rescued only
kind: 'PRIMARY'into "Other"; a generated personal page iskind: 'PERSONAL', matched no branch, and fell off the end of theforEach— in the very block whose comment says un-grouped perspectives are collected "instead of being silently dropped". The branch now rescues anykindandconsole.warns when it does.Tests
PerspectiveGroupAggregationIT(new, HTTP-only, ~30 s) publishes perspectives with a stalegroupIdand with none at all, and asserts both come back inside the shell's group and neither standalone; a second test asserts theundefined-groupfallback still applies where no default is declared. Verified to fail against the pre-fix aggregator (the stale/groupless perspective never appears) and pass with it.IntentEmissionCoverageITnow asserts each shell perspective is inside its group (perspectives.find { it.id == 'personal' }.items.id) rather than merely present somewhere in the response, and that no group id is baked into the generated artifact. This is the issue's reusable lesson: for an artifact whose value is a constant the platform must agree with, assert the constant.Both green locally (
-Dit.test="PerspectiveGroupAggregationIT,IntentEmissionCoverageIT"), plus a fullmvn -T 1C clean install -P quick-build.🤖 Generated with Claude Code