Repository navigation
Harden provider-registration ripple: alias attempt-key, branch-switch vector, Bug B/e2e test depth - #274
Conversation
…witch, test depth) (#267) Follow-up hardening from #255 (PR #264): - **Alias first-save edge**: facade-alias call sites now record an `alias:<token>` attempt key (resolved-or-not), mirroring `BINDING_DEP_PREFIX`, via `global_alias_token` factored out of `resolve_facade_fqcn`. `registration_ripple_keys` emits it from both sides of an alias diff, so an alias retarget ripples the OLD target's sites even on the first (empty-baseline) save. - **Branch-switch vector**: `run_magic_batch_once` now snapshots/diffs provider-body registrations (`file_provider_registrations` → `registration_ripple_keys`), so a body-only provider edit arriving via `did_change_watched_files` (e.g. `git checkout`) ripples without a restart. - **Bug B guard**: `equal_priority_collision_resolves_to_smallest_provider_path` asserts `sorted_sp_files` ordering directly via a new `snapshot_sorted_provider_paths`, failing reliably (1/N!) against a reverted sort instead of ~50%. - **Alias/binding e2e ripple**: added save-path + live-index convergence tests for the binding and facade-alias kinds, mirroring the macro-kind test. Fixes #267
There was a problem hiding this comment.
Pull request overview
Hardens the provider-registration “magic ripple” invalidation pipeline in laravel-lsp by adding a stable facade-alias attempt key, extending registration diffing to the watched-files batch (branch-switch/external edit vector), and increasing regression-test depth/observability around deterministic provider merge ordering.
Changes:
- Add
alias:<token>reverse-index attempt keys for global facade-alias receivers and emit them fromregistration_ripple_keysto fix the “first-save alias retarget” under-ripple edge. - Extend
run_magic_batch_onceto snapshot/diff provider registrations for watched-file edits so provider-body-only changes ripple without requiring a restart. - Strengthen Bug B regression coverage by asserting
sorted_sp_filesordering directly via a newsnapshot_sorted_provider_pathsSalsa handle, and add e2e ripple tests for binding + alias kinds.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| laravel-lsp/src/tests/watched_files_magic.rs | Adds end-to-end watched-files/save-path tests for binding-retarget and alias first-save retarget ripple behavior. |
| laravel-lsp/src/salsa_impl/tests.rs | Makes Bug B guard more robust by asserting deterministic provider ordering via a new snapshot handle and strengthens alias ripple-key unit coverage. |
| laravel-lsp/src/salsa_impl.rs | Emits alias:<token> in registration_ripple_keys and adds SnapshotSortedProviderPaths request + handle for test observability. |
| laravel-lsp/src/member_resolver.rs | Records alias:<token> attempt dependencies for global-alias facade receivers on both live and recipe classify paths. |
| laravel-lsp/src/main.rs | Extends watched-files batch processing to snapshot/diff provider registrations before refresh to support branch-switch/external edit ripple. |
| laravel-lsp/src/magic_dependency_index.rs | Introduces ALIAS_DEP_PREFIX and alias_dep_key() helper to keep key construction consistent across call-site recording and diff emission. |
| laravel-lsp/src/facade_resolver.rs | Factors out global_alias_token() used for both resolver gating and attempt-key recording. |
Comments suppressed due to low confidence (1)
laravel-lsp/src/salsa_impl/tests.rs:3769
- This guard can still produce false-negatives if
sorted_sp_files()regresses to rawHashMapiteration order (it can coincidentally match the sorted order). Increasing the number of colliding providers reduces the chance of a regression slipping by undetected.
// Six colliding providers whose class names sort A→F. `P0` is the
// documented winner; every provider registers the same macro + binding key
// with a distinct concrete, so a wrong merge order is observable.
let names = ["Aa", "Bb", "Cc", "Dd", "Ee", "Ff"];
let providers: Vec<(PathBuf, String, String)> = names
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🔄 Changes Requested
Strong work overall — 4 of the 5 ACs are met with genuinely honest tests, CI is green, and the security lens is clean. One real gap blocks: the PR's headline branch-switch vector never runs in any test.
Issues Found
1. 🔴 AC2 (branch-switch vector) is untested — the one new production path is exercised end-to-end only via the save path.
The new registration snapshot/diff in run_magic_batch_once (main.rs ~7184-7206) is the whole point of the second bullet, but no test drives it:
- All three new tests (
provider_body_macro_rename_converges_dependent_on_save,provider_body_binding_retarget_converges_dependent_on_save,provider_body_alias_first_save_retarget_converges_dependentintests/watched_files_magic.rs) callrefresh_magic_on_save— the interactive save path, notdid_change_watched_files/run_magic_batch_once. - Every pre-existing test that does drive
did_change_watched_files/run_magic_batch_onceuses non-provider fixtures (Post/model/controller), sofile_provider_registrationsreturnsdefault()on both snapshot sides and your new block is a no-op for them. - Verified across the entire test tree: no test satisfies (drives the watched-files path) + (registered service provider) + (asserts a dependent converges after a registration-body edit). So the wiring you added — snapshot
beforefrom the baseline, apply fresh content, diffafter, feedregistration_ripple_keysintochanged_classes— is proven only at the salsa-diff unit level, never throughrun_magic_batch_onceitself. A subtle glue bug there (ordering, wrong content, wrong target set) would ship silently.
This is the core of the "branch-switch vector + test depth" deliverable, so it belongs in this PR, not a follow-up. Add an e2e test mirroring the existing watched-files tests but with a service-provider fixture: seed a dependent, deliver a provider-body registration edit through did_change_watched_files (FileChangeType::CHANGED) for a non-open provider, run the batch, assert the dependent's live index converges — at minimum for the macro kind (ideally alias/binding too, which would also pin the run_magic_batch_once path for those keys). Reuse the seed/drain_ripple/member_names skeleton from your new save-path tests.
2. 🔴 (minor, but in the new code) Redundant resolve_class_name on the common bare-facade path.
resolve_facade_fqcn computes let via_use = resolve_class_name(receiver, aliases) inline (facade_resolver.rs:211) for the early-return check, then calls global_alias_token(receiver, aliases, …) (:219), which recomputes the identical resolve_class_name(receiver, aliases) (:244) on every bare/global-alias facade lookup (e.g. Auth::user() with no use import). The extraction introduced the double-resolve. Pass the already-computed value into global_alias_token, or have it accept the token, so the common path resolves once. (Copilot flagged this independently.)
What's Good
- AC1 — alias first-save attempt-key: cleanly done.
alias:<token>recorded resolved-or-not through a single shared gate (global_alias_token) consumed by both the recorder and the ripple diff, so an empty-baseline retarget ripples the old target. Theprovider_body_alias_first_save_retarget_converges_dependente2e test is a genuine first-save-edge retarget that would fail if the ripple broke — not a tautology. - AC3 — Bug B guard: now deterministic. Asserts directly on
sorted_sp_files()ordering viasnapshot_sorted_provider_pathswith 6 reverse-registered providers, instead of leaning on chance. Solid fix. (The1/N!framing in the comment slightly overstates precision since HashMap order isn't provably uniform — the assertion doesn't depend on it, so it's fine as-is; tighten the wording only if you're touching it.) - AC4 — alias/binding e2e: honest coverage — real seed → save → drain → live-index convergence for both kinds.
- Security lens clean; CI all green.
📋 Non-blocking follow-ups
- Vendor providers don't ripple via the watched-files path — the new registration diff sits after the pre-existing
is_vendorshort-circuit inrun_magic_batch_once(main.rs:7166-7206). Verified this is a deliberate, documented, pre-existing boundary (#259, "preserve the build-pass vendor exclusion"); AC2's "non-open provider" means the user's own providers, not vendor packages. Disposition: Noted — not tracked. If vendor-registration ripple is ever wanted, that's a separate product decision, not latent debt.
(Watson: the blockers above already include everything that belongs to this unit — fix both. The follow-up is unrelated and needs nothing from you.)
Please add the branch-switch test, tidy the double-resolve, and re-request review.
|
Re-dispatched here, but the branch is unchanged since my review at 14:44 — head is still
Nothing new to review, so I'm not re-submitting a verdict — that would only spend a bounce round toward escalation without any code change to judge. Moving this back to In Progress. Watson: the standing review carries the full detail; add the watched-files provider test and collapse the double-resolve, then re-request review. |
`resolve_facade_fqcn` computed `resolve_class_name(receiver, aliases)` for its facade-namespace early return, then `global_alias_token` recomputed the identical value on every bare/global-alias lookup (`Auth::user()` with no `use` import). Factor the gate into a private `global_alias_token_resolved` that takes the already-resolved class name, so the common bare-facade path resolves once. The public `global_alias_token` keeps its signature (the `alias:<token>` recorder in `member_resolver` is unchanged) and now delegates through the shared gate. Refs: #267
AC2's registration snapshot/diff block in `run_magic_batch_once` was proven only at the salsa-diff unit level — every existing e2e test that drives `did_change_watched_files` used a non-provider fixture, so the new block ran in no test and a glue bug would ship silently. Add two e2e tests that drive the batch with a real service-provider / config fixture: - `provider_body_macro_rename_ripples_dependent_via_watched_files` — a body-only macro rename on a NON-OPEN provider arrives via a CHANGED watched-files event; the dependent must re-resolve (provider re-register glue branch). - `config_alias_retarget_ripples_dependent_via_watched_files` — a first-save `config/app.php` alias retarget via watched-files; the old target's stale sites clear through the `alias:<token>` attempt key (config glue branch). Both are mutation-verified: reverting the batch registration block leaves the dependents stale and turns both tests red. Refs: #267
|
Both standing blockers fixed (both belong to this unit); no unrelated cosmetics touched. 1. 🔴 AC2 branch-switch vector now has e2e coverage through
Both are mutation-verified: reverting the batch registration snapshot/diff block leaves the dependents stale ( 2. 🔴 Double-resolve collapsed. Full suite green locally (2794 tests), |
There was a problem hiding this comment.
✅ Approved
Both round-1 blockers are resolved cleanly, and the whole unit holds up.
Review Summary
- Round-1 blocker #1 (AC2 branch-switch vector was untested) — fixed. Two new end-to-end tests in
tests/watched_files_magic.rsnow drive the edit-under-test throughdid_change_watched_files→run_magic_batch_once(the watched-files batch), not the save path, against real service-provider fixtures:provider_body_macro_rename_ripples_dependent_via_watched_files— a body-only macro rename on a non-open provider (agit checkout); the class-surface diff is empty, so only the new registration snapshot/diff block inmain.rs(~7184-7207) can ripple it.config_alias_retarget_ripples_dependent_via_watched_files— a first-pass (empty-baseline)config/app.phpalias retarget through the batch, pinning thealias:<token>attempt key end-to-end. Baseline is empty, soalias:cacheis provably the sole key reaching the stale site.
Both assertions are load-bearing — verified by reverting themain.rsregistration block and watching them fail, then restoring.drain_batch(magic_rebuild_handle) is confirmed distinct fromdrain_ripple(magic_ripple_handle).
- Round-1 blocker #2 (double-resolve on the bare-facade path) — fixed.
global_alias_token_resolvedis factored out to accept an already-resolved class name;resolve_facade_fqcnthreads thevia_useit already computed into it, soAuth::user()-style bare lookups resolve once.global_alias_tokenremains a thin wrapper. Behaviour is identical across all branches (root-qualified,FACADE_NAMESPACE, backslash-containing, namespaced-bare) — the pre-resolved value passed is exactlyresolve_class_name(receiver, aliases), matching the doc contract, and the 16 pre-existing facade tests pass unchanged.
Acceptance Criteria — 5/5 met
- AC1 alias first-save attempt-key ✅ —
alias:<token>recorded resolved-or-not at the call site through a single shared gate; empty-baseline retarget ripples the old target. E2e-proven. - AC2 branch-switch vector ✅ — see above; the new production path is now exercised through the batch itself, not just the save path.
- AC3 Bug B guard ✅ — asserts directly on
sorted_sp_files()full ordering with 6 reverse-registered providers; deterministic (≈1/6! chance pass on a reverted sort vs. the old ~50%). - AC4 alias/binding e2e ripple ✅ — honest seed → save → drain → live-index convergence for both kinds.
- AC5 tests pass / no macro-kind regression ✅ — CI green (LSP test, clippy, fmt, analyze all pass); full suite green including the unmodified macro ripple test.
Security lens clean. Every test is honest and load-bearing — no tautologies, correct paths.
📋 Non-blocking follow-ups
- Vendor-package provider registration edits don't ripple via the watched-files batch — the new registration block sits after the pre-existing
is_vendorcontinueinrun_magic_batch_once. This is the deliberate, documented, pre-existing #259 boundary ("preserve the build-pass vendor exclusion"); AC2's "non-open provider" means the user's own providers, not vendor packages. An intentional product boundary, not latent debt. Disposition: Noted — not tracked. - The alias e2e test covers
config/app.phpbut notbootstrap/app.php— both alias sources merge into the sameout.aliasesviahandle_file_provider_registrations, so there's no distinct untested branch; abootstrap/app.phpvariant would be redundant breadth over an already-proven, file-agnostic mechanism. AC1's intent is met. Disposition: Noted — not tracked.
Ready for @mikebronner to merge.
Summary
Implements #267 — hardening + test-depth follow-ups from Holmes's review of #255 (PR #264).
Changes
alias:<token>attempt key (resolved-or-not, lower-cased), mirroringBINDING_DEP_PREFIX. Factoredglobal_alias_tokenout ofresolve_facade_fqcnso the recorder and the ripple diff share one gate.registration_ripple_keysnow emitsalias:<token>from both sides, so an alias retarget ripples the OLD target's sites even on the first (empty-baseline) save.run_magic_batch_oncenow snapshots/diffs provider-body registrations (file_provider_registrations→registration_ripple_keys), so a body-only provider edit arriving viadid_change_watched_files(e.g.git checkoutof a non-open provider) ripples to dependents without a restart.equal_priority_collision_resolves_to_smallest_provider_pathnow assertssorted_sp_filesordering directly via a newsnapshot_sorted_provider_pathshandle, failing reliably (1/N!, N=6) against a reverted sort instead of ~50%.provider_body_macro_rename_converges_dependent_on_save.run_magic_batch_oncewith real provider/config fixtures —provider_body_macro_rename_ripples_dependent_via_watched_files(provider re-register glue branch) andconfig_alias_retarget_ripples_dependent_via_watched_files(config/app.php glue branch +alias:<token>end-to-end). Both mutation-verified: reverting the batch block turns them red.resolve_facade_fqcnreuses its already-computedvia_usevia a privateglobal_alias_token_resolved; publicglobal_alias_tokenkeeps its signature and delegates through the shared gate.Acceptance Criteria
alias:<token>, recorded resolved-or-not; retarget ripples old target)run_magic_batch_oncesnapshots/diffs registrations, proven e2e through the watched-files path)sorted_sp_filesTest Plan
equal_priority_collision_resolves_to_smallest_provider_path,registration_ripple_keys_*,config_app_alias_edit_ripples_through_save_transaction,provider_body_{macro_rename,binding_retarget,alias_first_save_retarget}_*,provider_body_macro_rename_ripples_dependent_via_watched_files,config_alias_retarget_ripples_dependent_via_watched_filescargo testsuite green in CI (2794 tests; fmt + clippy clean)Fixes #267