A Server.LLM node created with swarmEnabled=true and a costScoreSource
pointing at a DataPoint/Calculation never bid on any Swarm.Work. The work
sat OPEN forever with no error anywhere. Reading the node back showed
costScore: -1.0, advertisedAt: 0, and — even though the POST body
included sources: [<solarCost>, <daylight>] — sources: []. Pressing the
node’s ADVERTISE action once fixed it immediately: costScore became a
real number and the node started winning auctions. Nothing in the create
path did the equivalent of pressing that button, and nothing persisted the
sources wiring the client had posted.
Two independent defects that compounded:
1. costScore starts at -1 and nothing folds it at create time.
SwarmAdvert.effectiveCost returns the stored costScore whenever
costScoreSource != null, and that field defaults to -1.
SwarmAdvert.isAccepting then rejects any effectiveCost < 0. The only
code path that ever recomputes costScore from the wired source is
ServerLLMProcessor.republishAdvert, invoked solely by an explicit
ADVERTISE action or by the source itself firing dispatchExecute. Node
creation triggered neither.
2. sources were silently dropped on every Server.LLM create.
ServerNodeManager.update() runs newly-created child metadata through
NodeMetaData.resolveCreatedChildWiring(), whose job is to stop a node from
auto- or explicitly-observing a non-data-emitting Project/Server parent
(a Project dropped under a Server, or any node dropped under a
Project, must not treat that parent as a source). The check for this was
nodeType is KrillApp.Server || parentType is KrillApp.Server. Since every
Server.LLM (like Server.Pin / Server.SerialDevice) is a child of an
actual Server node in the tree, parentType is KrillApp.Server was true
for every LLM node, all the time — not just when the client tried to name
the Server itself as a source. When true, the old code called
clearedWiring(), which wipes all of sources/invocationTriggers
unconditionally, not just a reference to the parent. So an LLM’s
costScoreSource/acceptWindowSource wake-wiring — which has nothing to do
with observing the Server parent — was nuked on every single creation.
NodeMetaData.resolveCreatedChildWiring() (shared/.../node/NodeMetaDataSourceWiring.kt)
now filters the disallowed parent identity out of sources instead of
clearing the whole list. Posting the Server/Project parent itself as a
source still gets stripped (unchanged behavior, still covered by the
existing client-posted project source is cleared test); any other
source the client posted — e.g. an LLM’s cost/window DataPoint — survives.
invocationTriggers is only cleared when the filtered sources list ends
up empty, so a real wiring’s trigger isn’t dropped alongside sources that
are still present.ServerNodeManager.update() now folds a new swarm-enabled LLM’s advert
immediately after persisting it: if the just-created node’s meta is an
LLMMetaData with swarmEnabled=true and costScoreSource != null, it
calls invoke(node, by = node.id(), verb = NodeAction.ADVERTISE) — the
exact same path a manual ADVERTISE press uses. This is gated on
isNewNode (the same flag already used for the CREATED SSE broadcast),
so republishAdvert’s own settle-to-NONE re-entry into update() (where
the node now exists, so isNewNode is false) does not retrigger it.nodeType is X || parentType is X guard meant to catch “is this node
literally X” is only safe when X’s children are namespaced siblings
of X (like KrillApp.Project’s children, which do not extend
Project) — not when they’re genuine subtypes, or (as here) simply always
parented under an X node in the tree. Before writing a Kotlin is check
against a sealed hierarchy’s root, check what the check is actually meant
to exclude: “the literal node” is not the same condition as “this
type or anything nested under it in the tree.”