SIGN IN SIGN UP

feat(networkstream): ship trees once per process, capped, with the attribution marker (SUB-7786)

buildWireStream derives the HTTP payload from the flush snapshot: per-event trees
move into the message-scoped Processes map, one copy per distinct ProcessRef, and
each event keeps only its ref. Trees dominate the payload, so shipping them once
per process rather than once per connection is the size decision; the duplicated
ref bytes are budgeted. ProcessAttributionVersion is stamped unconditionally --
an empty Processes map is not a capability signal, so a sensor that ran and found
nothing must stay distinguishable from one that predates attribution.

On collision the deeper chain wins: the tree cache TTL (1 min) is shorter than
the flush interval (2 min), so two lookups for one process inside one interval
can return chains with different ancestry resolved. First-wins would discard the
richer chain and make the payload depend on map iteration order.

Command lines are capped at 1024 bytes with a visible marker, never splitting a
rune. cmdline is ~40% of a tree's bytes and unbounded, so this is what bounds the
payload tail rather than an optimisation.

The copy is read-only by hand rather than via armotypes.Process.DeepCopy, which
mutates its receiver: it calls MigrateToMap -- allocating ChildrenMap and nilling
Children -- on itself and on every child it recurses into. Those nodes are shared
with the process-tree manager's LRU cache, the legacy alert paths and the
notification-channel consumer, so writing to them from the flush goroutine would
be a data race that also strips the uncapped values those consumers rely on.
copyCappedProcess normalises the deprecated Children slice on read instead, and
every walk is depth-bounded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Alon <alon@armosec.io>
A
Alon committed
29f246e184efbdbacaf4764b8f21e6852d11cd73
Parent: 6f5c8dd