Skip to content

Refactor: harden contracts before v2.0.0 — reserved Context key, final Store, strict feature validation - #10

Merged
antfroger merged 6 commits into
mainfrom
small-refactoring
Sep 10, 2026
Merged

Refactor: harden contracts before v2.0.0 — reserved Context key, final Store, strict feature validation#10
antfroger merged 6 commits into
mainfrom
small-refactoring

Conversation

@antfroger

Copy link
Copy Markdown
Owner

Summary

Final pass of contract hardening before tagging v2.0.0.

⚠️ Breaking changes

  • The "rules" key is reserved in the user's Context. Progressive
    injects the Store into the Context under this key. Any user-defined
    parameter with that name now throws a LogicException at construction
    instead of being silently overwritten. Exposed as StoreInterface::BAG_KEY
    (@internal).
  • Store is now final. The built-in rules (enabled, partial,
    unanimous) are part of the config language: they are always loaded and
    can never be removed or shadowed. Extension is composition-only via the
    constructor: new Store([$myRule, ...]). The protected load() hook
    (which allowed subclasses to remove built-ins) and the unused
    Store::list() are removed.
  • Malformed feature declarations are rejected at construction time.
    A feature must be a boolean or an array containing exactly one rule or
    strategy. Previously, '', 42, [] passed validation and silently
    evaluated to false at runtime.

Fixed

  • Typo in Validator exception message ("startegy")
  • Removed unused Store::list()
  • Replaced inline @var StoreInterface documentation with a runtime
    assert() in Partial/Unanimous (type narration now enforced, not asserted by faith)
  • Unit tests: built-in rules always loaded, extra rules appended,
    built-in override rejection, reserved key rejection, strict feature validation

The Store is injected into the Context under the key "rules", which silently overwrites any user-defined parameter with the same name.
The key is now reserved: constructing a Progressive with a Context containing a "rules" parameter throws a LogicException instead of overwriting it.

The key is exposed as StoreInterface::BAG_KEY (@internal).
Store is now final: the built-in rules (enabled, partial, unanimous) are part of the config language and can never be removed, overridden or shadowed.
Extension is composition-only, via the new constructor
parameter:

    new Store([$myRule, ...])

Extra rules are appended after the built-in ones. Passing a rule whose name collides with a built-in throws a LogicException.

The protected load() hook is removed (replaced by the private getBuiltInRules()): subclasses could previously remove built-in rules, which contradicts the library's contract.
A feature must now be either a boolean or an array containing exactly one rule or strategy. Previously, empty arrays, scalars and other malformed values passed validation and silently evaluated to false at
runtime, masking misconfigurations instead of surfacing them.

Note: the rule's *parameters* are unaffected; a rule declared with empty params (e.g. `['env' => []]`) remains valid.
@antfroger
antfroger marked this pull request as ready for review September 10, 2026 14:07
@antfroger
antfroger merged commit 00ce050 into main Sep 10, 2026
16 checks passed
@antfroger
antfroger deleted the small-refactoring branch September 10, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant