Skip to content

gate-14 invariant 1 greps routes.php for a literal, so every route supplied by AppHost Routes::standard() reads as unrouted #223

Description

@rubenvdlinde

Summary

gate-14 (route-reachability) invariant 1 decides "this controller method has no route" by grepping appinfo/routes.php for the literal string 'controller#method':

hydra-gates/scripts/run-hydra-gates.sh (at 756fe89), line 1539:

if ! grep -qF "'${_ctrl_slug}#${_m}'" appinfo/routes.php; then
    echo "... rule=missing-route" >> "${_rr_log}"

An ADR-040 AppHost leaf does not write those literals. Its appinfo/routes.php returns \OCA\OpenRegister\AppHost\Routes::standard([...]), and the engine supplies the canonical set. So every canonical route reads as unrouted — a 404 claim about endpoints that are demonstrably served.

The gate already has the machinery for the other direction: _HYDRA_APPHOST / _apphost_serves / _di_binds_controller (added in #199) cover invariant 2, where a route names a controller file that is absent because AppHost aliases it. Invariant 1 has no equivalent. It fires precisely when the leaf does ship the controller (so aliasControllerUnlessLeafDefinesIt keeps the leaf's own class) and lets the engine register the route.

Measured on ConductionNL/openbuild @ c70f7b8d9

Full-tree run, no diff scoping:

[gate-14] route-reachability: FAIL — 7 unrouted method(s) or wrong-target route(s)

lib/Controller/DashboardController.php   method=catchAll        expected_route='dashboard#catchAll'
lib/Controller/DashboardController.php   method=page            expected_route='dashboard#page'
lib/Controller/PreferencesController.php method=getPreference   expected_route='preferences#getPreference'
lib/Controller/PreferencesController.php method=setPreference   expected_route='preferences#setPreference'
lib/Controller/SettingsController.php    method=create          expected_route='settings#create'
lib/Controller/SettingsController.php    method=index           expected_route='settings#index'
lib/Controller/SettingsController.php    method=load            expected_route='settings#load'

All 7 of 7 are the engine-supplied canonical set. Call path:

  1. openbuild/appinfo/routes.php — the whole file is return \OCA\OpenRegister\AppHost\Routes::standard([ ...domain routes... ]);
  2. openregister/lib/AppHost/Routes.php emits, verbatim:
['name' => 'dashboard#page',              'url' => '/',                        'verb' => 'GET'],
['name' => 'settings#index',              'url' => '/api/settings',            'verb' => 'GET'],
['name' => 'settings#create',             'url' => '/api/settings',            'verb' => 'POST'],
['name' => 'settings#update',             'url' => '/api/settings',            'verb' => 'PUT'],
['name' => 'settings#load',               'url' => '/api/settings/load',       'verb' => 'POST'],
['name' => 'preferences#getPreference',   'url' => '/api/preferences/{key}',   'verb' => 'GET'],
['name' => 'preferences#setPreference',   'url' => '/api/preferences/{key}',   'verb' => 'PUT'],
['name' => 'metrics#index',               'url' => '/api/metrics',             'verb' => 'GET'],
['name' => 'health#index',                'url' => '/api/health',              'verb' => 'GET'],

plus the SPA catch-all (dashboard#catchAll) appended last. Every one of the 7 findings is in that list. openbuild/appinfo/routes.php says so in a comment at the top, naming the exact set.

_HYDRA_APPHOST is already 1 for this repo — lib/AppInfo/Application.php both references OCA\OpenRegister\AppHost\Bootstrap and calls Bootstrap::register(. Invariant 1 simply never consults it.

Positive control

The gate is not blind — it is reading a real absence and drawing the wrong conclusion from it. grep -F "'dashboard#page'" appinfo/routes.php genuinely returns nothing, because the literal is in openregister. The finding is accurate about the file and wrong about the route table.

Why this matters beyond noise

gate-14 is in the authorisation tier. Seven standing false positives in that tier is the "cries wolf on correct code" shape #153 already fixed for gate-5 — it trains readers to skip the whole tier. It also cannot be silenced safely per-repo: adding the literals to appinfo/routes.php would register duplicate route names, and Routes::standard() throws on duplicates by design.

Suggested fix

Mirror what invariant 2 already does. When appinfo/routes.php calls \OCA\OpenRegister\AppHost\Routes::standard(, treat the canonical name list as routed for invariant 1 — as an explicit list (dashboard#page, dashboard#catchAll, settings#index|create|update|load, preferences#getPreference|setPreference, metrics#index, health#index), sourced from openregister/lib/AppHost/Routes.php, never a wildcard. A wildcard would let any genuinely unrouted method hide behind AppHost adoption, which is the invariant worth keeping.

Evidence that the narrow list is sufficient here: settings#update is in the canonical list and is not among openbuild's findings, because openbuild's own SettingsController does not define update(). The list-based exemption therefore does not need to suppress anything the leaf actually owns.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions