fix: correct copy semantics for string attributes
zig's struct copy semantics caused dangling pointers when Span.Data
was copied (in span.end()). the Value.string slice pointed to the
original struct's _string_storage, not the copy's.
fix: don't store a slice in Value.string at all. store string data in
_string_storage and reconstruct the slice via getString() on access.
this ensures the slice always points to the current struct's storage.
- Value.string is now just a tag (no payload)
- added getString() method to reconstruct slice from internal storage
- copyString() no longer returns a slice
- all consumers now use getString() instead of .string
- for loops capture |*attr| to get pointers
added test verifying copy safety: the getString() pointer comes from
the copy's _string_storage, not the original's.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>