firefly/actuator is LaraFly's production-ready management surface — the Spring-Boot-Actuator analogue. It ships a
HealthIndicator SPI with liveness/readiness probe groups, an ActuatorEndpoint contract + registry, and a
route-registration BootPass that mounts framework endpoints on the illuminate Router under /actuator. Dependency-light,
always-on, and secured entirely by M11 config with zero code edge to firefly/security.
/actuator— HAL index of exposed endpoints/actuator/health(+/actuator/health/{group}, liveness/readiness) — aggregated health, 503 on DOWN/actuator/info— mergedInfoContributorfragments (runtime,app,build)/actuator/env— thefirefly.*config tree, sensitive values masked/actuator/configprops— every#[ConfigProperties]DTO, with the values it actually resolved off the bound instance, masked/actuator/caches(+/actuator/caches/{name}) — the configuredcache.stores(name/driver/default only); read-only, no eviction/actuator/beans,/actuator/conditions,/actuator/mappings,/actuator/loggers(GET/POST),/actuator/scheduledtasks/actuator/metrics,/actuator/prometheus,/actuator/httpexchanges,/actuator/process— supplied byfirefly/observabilitywhen installed
!!! tip "A browser view over all of this"
firefly/admin renders these same endpoints as a server-side dashboard, reading them in-process rather
than over HTTP — so it shows pages the exposure model below deliberately keeps unpublished. That inversion is
the whole of its security model: see Admin Dashboard, and read
its access model before enabling it outside app.debug.
HealthIndicator { health(): Health }; Status = UP/DOWN/OUT_OF_SERVICE/UNKNOWN (severity DOWN>OUT_OF_SERVICE>UP>UNKNOWN);
a most-severe StatusAggregator (empty→UP); a bean-scan HealthContributorRegistrar; built-in Ping/DiskSpace/Db
indicators. A throwing indicator degrades to DOWN — never a 500.
Default firefly.management.endpoints.web.exposure.include = "health,info"; sensitive endpoints return 404 until
explicitly exposed. An endpoint body is always a JSON object: /actuator/info with no InfoContributor
registered answers {}, not [], so a typed client deserialising into a map does not break on the default. Lock them down with firefly.security.http.rules (no second management port — doesn't fit PHP-FPM):
'firefly' => [
'security' => [
'enabled' => true,
'http' => [
'enabled' => true,
'rules' => [
['pattern' => 'actuator/health', 'access' => 'permitAll'],
['pattern' => 'actuator/info', 'access' => 'permitAll'],
['pattern' => 'actuator/*', 'access' => 'hasRole:ACTUATOR'],
],
],
],
],firefly/security's HttpSecurityFilter is a global middleware — FilterChainRegistrar pushes it onto Laravel's
HTTP-kernel middleware stack ($kernel->pushMiddleware(...)), so it runs for every request the kernel handles,
including the actuator's directly-$router->match()-registered /actuator/** routes. Securing actuator is therefore
pure configuration: firefly/actuator's composer.json has no dependency on firefly/security, and deptrac.yaml
carries no Actuator → Security edge. An integration test
(packages/actuator/tests/SecurityLockdownIntegrationTest.php, booted via
packages/actuator/tests/Support/SecuredActuatorCapstoneTestCase.php) boots both stacks together and proves it
end-to-end: with the lockdown rules above and env exposed, an anonymous GET /actuator/env is denied 401
(AuthenticationException, no matching rule's expression is satisfied) while GET /actuator/health stays 200
(permitAll).
/actuator/env and /actuator/configprops additionally mask any key matching password|secret|token|key|credential|passwd
(case-insensitive, recursive — the shared SensitiveValueMasker) with ******. The key is tested before the value's
type, so a sensitive key holding an array (a JWT keyring, a credentials pair) is replaced wholesale rather than recursed
into, independent of whether the URL lockdown above is configured — defense in depth for an
endpoint that is reachable at all only once explicitly exposed.
firefly.management.enabled(defaulttrue) — master gatefirefly.management.endpoints.web.exposure.include/.exclude(CSV or*;*is a wildcard in both lists and exclude wins, so.exclude = "*"is the kill switch)firefly.management.endpoints.web.base-path(default/actuator)firefly.management.endpoint.{id}.enabled(per-endpoint)firefly.management.endpoint.health.show-details(defaultnever; only the literalalwaysshows component details — see Known-latent forwhen-authorized)firefly.management.endpoint.health.group.{name}.includefirefly.management.endpoint.health.db.enabled(defaultfalse) — opt-inDbhealth indicatorfirefly.management.info.app.*,firefly.management.info.build.pathfirefly.management.info.runtime.enabled(defaulttrue,#[ConditionalOnProperty(matchIfMissing: true)]) — theruntimefragment of/actuator/info(PHP version/SAPI/OPcache, Laravel version, LaraFly version, current+peak memory). Setting itfalseremoves the contributor bean entirely.
Framework endpoints are #[Component] beans mounted by a BootPass on the illuminate Router (reusing route:list,
URL generation, the HTTP-kernel middleware pipeline) — not app controllers. Health/info reuse Laravel DB/Log/config.
when-authorizedshow-details degrades tonever(no Security code edge; gate details via the lockdown)./refresh,/threaddump,/shutdownare deferred to later SP cycles./cachesis read-only: Spring'sDELETEeviction is deliberately not implemented, becausefirefly/actuatorcarries no code edge tofirefly/securityand so cannot say who asked; aPOSTto it answers 404.- No second management port (an Octane second-listener is an SP-7 option).