Commit 11866c1
authored
feat: pipe.broadcast cross-scope push + collect reads exposed (#623)
## Summary
Adds a cross-scope **broadcast** primitive to the pipe/quirk system,
makes `collect` compose with `expose`, and fixes config-thunk resolution
to honour the **producing** class + scope — then makes that resolution
**class-neutral** so it no longer assumes NixOS.
### `pipe.broadcast pred` — push primitive (dual of `pipe.expose`)
Where `pipe.expose` routes a value **up to the parent** applying
source-side stages, `pipe.broadcast` routes the (transformed) value
**laterally to every other scope matching a receiver predicate**,
fleet-wide. Receivers read the pipe normally — no `collect` policy
required.
```nix
den.policies.broadcast-dev = { host, user, ... }:
[ (pipe.from "peer-dev" [
(pipe.transform redact) # source-side, applied before send
(pipe.broadcast ({ user, ... }: true)) # → every user scope, fleet-wide
]) ];
```
- **Receiver-only predicate** (same signature as `collectAll`); selects
receivers by their own context — kind, name, tags, `host`. Compound
predicates like `{ host, user, ... }: host.name == "edge"` target user
scopes on a specific host.
- **Self-excluded** (`S≠R`) — a source represents itself via its own
base.
- Implemented as `collectAllBroadcast` (Pass 1b) mirroring
`collectAllExposed`; reuses `findMatchingAll` entity-kind filtering.
`bindsPipeLocally` gains a broadcast clause so a pure receiver doesn't
fall through to ancestor inheritance.
### `collect`/`collectAll` read raw **+ exposed**
`collectTagged` now reads `resolveThunks(raw) ++ allExposed.${sid}`, so
a peer's collect sees data that children `pipe.expose`d up into a host.
Fixes the expose-then-fleet-collect path. `allExposed` is Pass-1 data →
no eval cycle.
### Producer-class config-thunk resolution
A pipe config-thunk now resolves against the **producing** class module
+ scope, not the consuming one (the prior behaviour was a latent bug — a
host-produced thunk consumed in a home resolved against the home
config).
- **Cross-scope** (broadcast/collect): the source's thunk resolves
against the producer's class config.
- **Deferred** (`__configThunk`): the marker carries the producer's
class + name; the class-module wrapper resolves against the producing
class's config and hands each thunk both `config` (producer class) and
the **owner** config (the enclosing host), mirroring home-manager. The
owner arg is requested from the module system only when a marker needs
it, so standalone homes keep working.
- `isConfigDependent` also detects owner-config-only thunks.
### Class-neutral resolution (no host/osConfig hard-coding)
The host↔producer navigation is **registry-driven**, not baked into core
fx — so den can describe a config-owner that isn't a NixOS host (e.g.
terranix, nixidy) without core edits:
- `den.classes.<class>.parentPath` — `name → path` locating a member
within the enclosing config-**owner** (the same route its content is
delivered to); `den.classes.<class>.parentArg` — the module arg a nested
member reaches the owner by. Both `null` for **root** classes that own a
top-level config (nixos, darwin, …). The home-manager battery registers
`parentPath` + `parentArg = "osConfig"` from the same value as its
forward-delivery path.
- `producerConfigs`/`resolveEntry` (assemble-pipes) and the class-module
wrapper navigate via the registry, keyed by `scopeEntityClass`.
`isConfigDependent` detects `config` or **any** registered `parentArg`.
**Zero `host`/`osConfig`/`home-manager` literals remain in core fx.**
- `hostConfigs` was already generic over instantiates (any entity with
an `intoAttr` output), so the model already supported non-host owners;
this removes the remaining NixOS-flavored literals. (Building actual
terranix/nixidy entity/class defs is follow-on work this unblocks; the
two soft `or "nixos"` class-default fallbacks are left intact.)
## Tests
- **`pipe-broadcast`** (10): all-to-all · user→remote-host ·
source-transform · predicate scoping (incl. `{ host, user }` compound) ·
self-exclusion · targeted no-leak · pure-receiver binding · config-thunk
from host / user source.
- **`pipe-broadcast-isolation`** (4): cross-pipe-name ·
host-target-excludes-home (entity-kind via shared `host` context) ·
no-match predicate · broadcast↔collect boundary (a fleet `collectAll`
counts each raw emit once, not the broadcast-amplified view).
- **`pipe-config-scope`** (3): host→home, user→own-home, user→host
producer-class resolution.
- **`pipe-scope`**: the `test-expose-then-fleet-collect` witness now
passes; the `exposed-config-thunk` witness reads the owner arg.
Full CI: **1042/1042**. `treefmt` clean.
## Commits
1. `pipe.broadcast` cross-scope push + collect reads exposed
2. producer-class config-thunk resolution + broadcast review fixes
3. derive config-thunk host navigation from the class registry
4. make config-thunk resolution class-neutral (no host/osConfig
literals)1 parent 5df0987 commit 11866c1
13 files changed
Lines changed: 1395 additions & 65 deletions
File tree
- modules
- aspects/batteries
- nix/lib
- aspects/fx
- edges
- templates/ci/modules
- internal-api
- public-api
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
9 | 17 | | |
10 | 18 | | |
11 | 19 | | |
12 | 20 | | |
13 | 21 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
| 22 | + | |
21 | 23 | | |
22 | 24 | | |
23 | 25 | | |
| |||
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
32 | 37 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
35 | 57 | | |
36 | 58 | | |
37 | 59 | | |
| |||
0 commit comments