Measured against the pinned @objectstack/lint@17.3.0 in node_modules (never the platform source tree), on objectstack-ai/hotcrm at db5fe702, as part of the step-3 rule survey (hotcrm#1637).
The fact
resolveUrlRoute scans a URL's segments for a key in URL_COLLECTION_TO_STACK_KEY:
var URL_COLLECTION_TO_STACK_KEY = {
object: "objects", objects: "objects",
report: "reports", reports: "reports",
dashboard: "dashboards", dashboards: "dashboards",
page: "pages", pages: "pages",
view: "views", views: "views"
};
apps is not a key, so the apps/NAME head of every in-app route is skipped and the app name is never resolved against stack.apps. An in-app URL whose app segment names nothing produces no finding, and — because the loop returns at the first recognised collection — the rest of the path is only reached when a later segment happens to be one of the ten keys above.
Repro, with the control in the same file, same site, same command
Both injections add one header action to src/dashboards/sales.dashboard.ts. objectstack lint --json, exit code captured before any pipe.
actionUrl on the injected actionType: 'url' header action |
lint |
finding |
/apps/crm_enterprise/dashboard/no_such_dashboard_nope |
exit 0 |
dashboard-action-route-unresolved (warning) at dashboards[3].header.actions[0].actionUrl |
/apps/no_such_app_nope/crm_lead |
exit 0 |
nothing — output byte-identical to the clean baseline (0 errors / 17 warnings / 12 suggestions) |
Same dashboard, same header.actions[0], same run design: the rule demonstrably fires at this exact site, and the unknown-app case is the one it cannot see.
For completeness, the sibling dashboard-action-target-undefined also fires here at error, exit 1 (actionType: 'script', actionUrl: 'no_such_action_nope'), so nothing about this dashboard or this header is inert.
Every injection was restored in the same run and proven restored by git hash-object equalling git rev-parse HEAD:PATH and an empty git status for that path; the clean-tree lint baseline reproduces byte-identically afterwards.
Why this is not a duplicate, and why it is worth a row
#3367 (closed) is the card that created these two rules. Its acceptance criteria are:
- A dashboard action referencing an undefined
script/modal action fails validation …
- A
url action pointing at a non-existent in-app route is at least warned.
The shipped implementation covers the collection half of "in-app route" and never the app half, and the app name is the one segment that is not optional in the route shape #3367 itself quotes (/apps/:appName/…). So this is unfiled residue of #3367's own criterion, not a new ask.
An app rename is the realistic way to reach it: every dashboard URL still carries the old app name, all of them 404, and lint stays green. A consuming app caught exactly this class by hand — its local guard rejects /apps/no_such_app/crm_lead — which is how the asymmetry showed up.
Searched and not duplicating: nothing matches the app-segment case (a targeted search returned zero, with two sibling searches in the same session returning 4 and 5 on-topic hits as the control that the search was not answering empty). Adjacent and checked: #3959 (modal action server dispatch), #4709 (app.homePageId dead branch), #3611 (view route dialects).
Suggested direction
Resolve the apps/NAME head against stack.apps before scanning for a collection key, and keep scanning the remaining segments rather than returning at the first match — today a bad app name plus a bad dashboard name reports only the second, and a bad app name alone reports neither.
Filed unassigned by the hotcrm epic seat (hotcrm#1579 step 3, survey part 3 — hotcrm#1637).
Measured against the pinned
@objectstack/lint@17.3.0innode_modules(never the platform source tree), onobjectstack-ai/hotcrmatdb5fe702, as part of the step-3 rule survey (hotcrm#1637).The fact
resolveUrlRoutescans a URL's segments for a key inURL_COLLECTION_TO_STACK_KEY:appsis not a key, so theapps/NAMEhead of every in-app route is skipped and the app name is never resolved againststack.apps. An in-app URL whose app segment names nothing produces no finding, and — because the loopreturns at the first recognised collection — the rest of the path is only reached when a later segment happens to be one of the ten keys above.Repro, with the control in the same file, same site, same command
Both injections add one header action to
src/dashboards/sales.dashboard.ts.objectstack lint --json, exit code captured before any pipe.actionUrlon the injectedactionType: 'url'header action/apps/crm_enterprise/dashboard/no_such_dashboard_nopedashboard-action-route-unresolved(warning) atdashboards[3].header.actions[0].actionUrl/apps/no_such_app_nope/crm_lead0 errors / 17 warnings / 12 suggestions)Same dashboard, same
header.actions[0], same run design: the rule demonstrably fires at this exact site, and the unknown-app case is the one it cannot see.For completeness, the sibling
dashboard-action-target-undefinedalso fires here at error, exit 1 (actionType: 'script',actionUrl: 'no_such_action_nope'), so nothing about this dashboard or this header is inert.Every injection was restored in the same run and proven restored by
git hash-objectequallinggit rev-parse HEAD:PATHand an emptygit statusfor that path; the clean-tree lint baseline reproduces byte-identically afterwards.Why this is not a duplicate, and why it is worth a row
#3367 (closed) is the card that created these two rules. Its acceptance criteria are:
The shipped implementation covers the collection half of "in-app route" and never the app half, and the app name is the one segment that is not optional in the route shape
#3367itself quotes (/apps/:appName/…). So this is unfiled residue of #3367's own criterion, not a new ask.An app rename is the realistic way to reach it: every dashboard URL still carries the old app name, all of them 404, and lint stays green. A consuming app caught exactly this class by hand — its local guard rejects
/apps/no_such_app/crm_lead— which is how the asymmetry showed up.Searched and not duplicating: nothing matches the app-segment case (a targeted search returned zero, with two sibling searches in the same session returning 4 and 5 on-topic hits as the control that the search was not answering empty). Adjacent and checked: #3959 (modal action server dispatch), #4709 (
app.homePageIddead branch), #3611 (view route dialects).Suggested direction
Resolve the
apps/NAMEhead againststack.appsbefore scanning for a collection key, and keep scanning the remaining segments rather than returning at the first match — today a bad app name plus a bad dashboard name reports only the second, and a bad app name alone reports neither.Filed unassigned by the hotcrm epic seat (hotcrm#1579 step 3, survey part 3 — hotcrm#1637).