fix(fx): surface spawn-projected quirk emits at the requesting scope#625
Draft
sini wants to merge 2 commits into
Draft
fix(fx): surface spawn-projected quirk emits at the requesting scope#625sini wants to merge 2 commits into
sini wants to merge 2 commits into
Conversation
A deferred `spawn` policy (e.g. the host-aspects battery) projects an aspect into a post-walk nested scope. The aspect's static quirk emit only materialized inside the spawned node, so a pipe policy (broadcast/collect/expose/local) at the scope that REQUESTED the projection read `[]` -- the projection did not behave "as if the requesting scope included the aspect directly". Pipe assembly runs once, pre-drain, over the raw walk imports (`scopedClassImportsRaw`); the spawn materializes later in the drain, so feeding its emit back into the raw map is cyclic. The projected quirk emit is static (no fleet dependency), so it can be surfaced before assembly: - spawn-node.nix: expose the spawn root's non-host-bound quirk keys as `quirkEmits` on the spawn return. Host-bound quirks are already stripped (`strippableNames`) and inherit the host's value, so they are not surfaced. - resolve.nix: hoist the spawn materialization into one shared binding (`homeNodeSpawns`), computed once over raw parent state -- invariant of the augmented contexts, and collapsing a previously-duplicated `spawnNode` sub-walk across the two `mkDrained` calls. Layer its `quirkEmits` over the raw imports into a SEPARATE `importsForPipes` map fed to both `assemblePipes` passes. `parentState` keeps the unmodified raw map, so the spawn's own internal assembly never re-reads the surfaced quirk (cycle + internal double-count avoidance). The spawn root is absent from the pre-drain scope universe, so the quirk lands exactly once at the requesting scope. Adds the reproducing test pipe-broadcast.test-broadcast-home-pool-to-host.
fd2c2a7 to
e6ee53c
Compare
A projected aspect's quirk must materialize at the requesting scope, visible to every pipe reader, exactly once per projection. New `pipe-projection` suite: - collect-projected-quirk-once: a user-scope `collectAll` sees it once (no spawn-root duplicate). - host-include-plus-projection-counted-once-each: a host include + a user projection of the same aspect each contribute exactly one emit; the host emit is not doubled into the user pool. - expose-projected-quirk-reaches-parent: `pipe.expose` lands it at the parent. - local-consume-projected-quirk: the same-scope `mkCombinedBase` reader gets it. - multi-class-spawn-surfaces-once: a multi-class spawn surfaces the class-agnostic quirk once (guards the `findFirst` dedup). - host-bound-projected-quirk-not-surfaced: a host-policy-bound quirk is not surfaced; the requesting scope inherits the host's value. - per-user-parametric-projection: two users on one host each projecting the same PARAMETRIC aspect resolve to distinct per-user values (no collapse/cross-bleed).
e6ee53c to
4911b7f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A deferred
spawnpolicy (e.g. thehost-aspectsbattery) projects an aspect into a post-walk nested scope. The aspect's static quirk emit only materialized inside the spawned node, so a pipe policy (broadcast/collect/expose/local) at the scope that requested the projection read[]— the projection did not behave "as if the requesting scope included the aspect directly".(Surfaced by replicating
~/.claudeacross a fleet via Syncthing: a user-scopepipe.broadcastof a host-aspects-projectedreplicateHomequirk returned empty.)Root cause
Pipe assembly runs once, pre-drain, over the raw walk imports (
scopedClassImportsRaw). The spawn materializes later in the drain (mkDrained), so the requesting scope has no entry for the quirk at assembly time — and every pipe reader takes its source straight from the imports map. Feeding the spawn's emit back into the raw map is cyclic (assembly → augmented → hostConfigs → drain → spawn).Fix
The projected quirk emit is static (no fleet dependency), so it can be surfaced before assembly:
spawn-node.nix: the spawn return gainsquirkEmits— the spawn root's non-host-bound quirk keys (host-bound ones are already stripped viastrippableNamesand inherit the host's value).resolve.nix: hoist the spawn materialization into one shared bindinghomeNodeSpawns(computed once over rawparentState— also collapsing a previously-duplicatedspawnNodesub-walk across the twomkDrainedcalls), then layer itsquirkEmitsover the raw imports into a separateimportsForPipesmap fed to bothassemblePipespasses.parentStatekeeps the unmodified raw map, so the spawn's own internal assembly never re-reads the surfaced quirk (cycle + internal double-count avoidance). The spawn root is absent from the pre-drain scope universe, so the quirk lands exactly once at the requesting scope.Net: the projected quirk materializes in both scopes — once at the requesting scope (visible to broadcast/collect/expose/local) and once in the spawned node (the existing nested consumer) — with no double-count.
Tests
pipe-broadcast.test-broadcast-home-pool-to-host.Draft — still adding the guard set: fleet-collect-sees-once, genuine-double-inclusion-counts-twice, expose, local emit-and-consume, multi-class spawn, host-bound-quirk boundary.