Skip to content

Stabilize light definition, lookup, and admin operations #793

Description

@bolshakov

The admin dashboard can crash or silently misreport a light's configuration. Its lock/unlock path obtains a light through the definition API (System#register(name)) with no per-light overrides. When the app registered the same light with overrides, the config-consistency digest check sees a conflict and raises Stoplight::Error::ConfigurationError -- every admin lock/unlock click crashes.

When admin runs in a separate process it instead silently registers a defaults-configured light: no crash, wrong config on display (the same happens in v5.x). And because the registry persists only {version, registered_at}, a separate-process admin has no way to learn a light's real configuration at all.

Root cause: the API conflated three verbs: define, look up, and operate, and admin is forced to define when it only needed to operate.

Design overview

The stabilized API gives each verb exactly one path:

Three supporting decisions make this work end to end:

  1. Matchers become named constants.

tracked_errors / skipped_errors accept any Class or Module with a non-nil .name - exception classes as the common case, named custom matchers overriding === for complex cases. Nameless matchers (procs, Class.new, instances) are rejected at definition time.

Reason: durable config is digested (cross-process, needs reload-stable identity), serialized (registry), and displayed (dashboard) all three need a stable string per matcher. This also fixes a bug (verified locally) where a Zeitwerk reload made the digest see "different" settings and raise an error.

  1. The restriction stops where its reasons stop.

Per-call tracked_errors: / skipped_errors: overrides on Light#run (issue #738, implemented separately) stay open to any === matcher, closures included: they are ephemeral, never digested or persisted, and the call site is the only place dynamic matching is expressible. Registered config is the durable identity of a light -- per-call arguments are ephemeral behavior.

  1. The registry persists real config, display-only.

Registration writes the light's actual settings to the registry, so a separate-process dashboard can show real config. Writes are last-write-wins with config drift reported through error_notifier as a warning -- never a raise, because processes re-register at boot and a raise would turn every rolling deploy into a boot crash. Persisted descriptors are for humans: nothing ever reconstructs them into live objects.

Lock/unlock also gains observability: the operation (color mapping, state write, LockChanged telemetry emission) is extracted into one domain object shared by Light and the admin, so both emit the same event and neither can drift from the other.

Summary

What we'll get when this is completed:

  • we can display configs in admin
  • Lights could be reloaded and operate normally
  • admin can run in the same process with the main application

Metadata

Metadata

Assignees

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions