Skip to content

fix(fx): surface spawn-projected quirk emits at the requesting scope#625

Draft
sini wants to merge 2 commits into
denful:mainfrom
sini:fix/broadcast-home-pool-to-host
Draft

fix(fx): surface spawn-projected quirk emits at the requesting scope#625
sini wants to merge 2 commits into
denful:mainfrom
sini:fix/broadcast-home-pool-to-host

Conversation

@sini

@sini sini commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

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".

(Surfaced by replicating ~/.claude across a fleet via Syncthing: a user-scope pipe.broadcast of a host-aspects-projected replicateHome quirk 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 gains quirkEmits — the spawn root's non-host-bound quirk keys (host-bound ones are already stripped via strippableNames and inherit the host's value).
  • resolve.nix: hoist the spawn materialization into one shared binding homeNodeSpawns (computed once over raw parentState — also collapsing a previously-duplicated spawnNode sub-walk across the two mkDrained calls), then 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.

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

  • Adds the reproducing test pipe-broadcast.test-broadcast-home-pool-to-host.
  • Full suite green: 1045/1045.

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.

@github-actions github-actions Bot added the allow-ci allow all CI integration tests label Jun 27, 2026
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.
@sini sini force-pushed the fix/broadcast-home-pool-to-host branch from fd2c2a7 to e6ee53c Compare June 27, 2026 03:22
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).
@sini sini force-pushed the fix/broadcast-home-pool-to-host branch from e6ee53c to 4911b7f Compare June 27, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

allow-ci allow all CI integration tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant