Skip to content

feat(plugin-contract): prove reload after unload #34

Description

@ss-o

Feature description

The portable plugin lifecycle assertions prove repeated source (load, load) and exact unload, but no case proves reload after unload (load, unload, load). A plugin can pass every current case and still be broken for the rest of the shell after its second load.

Related Code

tests/_support/plugin-contract/scenario.zsh on main (1dc51f1) walks source, source, unload in its ordinary scenario and never loads again after unloading. The README example has the same shape.

Reproduced today against a copy of the shipped fixture changed the way z-shell/z-a-meta-plugins#54 was broken: unload leaves the callback defined as an inert stub instead of removing it, and load only defines the callback when it is absent.

mutant: repeated-source check PASSES (does not catch it)
mutant: reload surface DIFFERS (caught by a reload case)
mutant: effect after reload MISSING (caught by an effect check)

The unmodified fixture passes the same load, unload, load walk: the reloaded surface equals the first load, its hook is registered again, and a second unload is exact.

Additional Context

This is the remaining mechanical deliverable of z-shell/.github#585, whose documentation half landed in z-shell/.github#687 and z-shell/wiki#927 (the standard now says: "In a separate clean-shell case, load, unload, and load again. Prove that the second load restores the same declared load surface as the first and that the restored resources perform their intended behavior."). #18, which added the assertion set, is closed.

Proposed change:

  • Add a reload scenario to tests/_support/plugin-contract/scenario.zsh: load, snapshot, unload, load again, snapshot, assert plugin_restored between the two loaded snapshots, then observe the effect of a restored resource (call it and check what it did), not only its presence.
  • Give the fixture a callback with an observable effect, so the effect check has something to observe.
  • Add the scenario to tests/plugin-contract.zunit in non-interactive and interactive shells, and a negative case with a fixture broken only after reload that must fail.
  • Show reload in the README's lifecycle example.

Acceptance criteria:

  • A fixture that is correct only on its first load fails the new case, and the shipped fixture passes it.
  • The reload case checks an observable effect, not presence or exit status.
  • ./zunit --tap tests passes on the native job.
  • The README lifecycle example includes reload after unload.

Self-service

  • I'd be willing to address this documentation request myself.

Have you read the Contributing Guidelines?

  • I have read the Contributing Guidelines.

Are you familiar with the Contributor Covenant Code of Conduct?

  • I have read the Contributor Covenant Code of Conduct.

Activity

  1. added
    type:featureA request for new behavior or capability.
    on Sep 30, 2026
  2. ss-o commented on Sep 30, 2026

    @ss-o
    MemberAuthor

    Project 28 triage pass. Blank values filled on this issue (existing values left unchanged):

    No area label: this repository defines no canonical area:* label that fits (only area:ci); adding one is label maintenance, not triage.

    Next action: implement on a feature-34 branch with a failing reload case first, then the fixture change.

  3. ss-o commented on Sep 30, 2026

    @ss-o
    MemberAuthor

    Progress: #35 implements every acceptance criterion here, on head 8799b23. All hosted checks pass, and a fallback review of record is on that head: #35 (review)

    Open decision (maintainer): the second review round found that the reload case never unloads after the second load, so a plugin whose unload leaks only after a reload passes every case. This is outside the criteria above. The choice is to extend #35 (one more push and review round) or to record it as a follow-up issue and merge as is.

    Status: In Progress until that decision is made; the pull request is In Review.

  4. ss-o commented on Sep 30, 2026

    @ss-o
    MemberAuthor

    Next action: maintainer decision on closure. #35 (merged, 6349b24) meets every acceptance criterion listed here: the reload scenario fails a fixture correct only on its first load (reload-stale), checks an observable effect, passes the native job, and the README shows reload. #35 used Refs by maintainer decision; the post-reload unload gap it found is #36.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:featureA request for new behavior or capability.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions