feat(leveva): IRCv3 message-tags foundation (P11 slice 56)
Add tag support to the core Message type and advertise the `message-tags`
capability — the prerequisite that unblocks the deferred slice-55 bot tag plus
server-time/message-ids/message-redaction/oper-tag.
message.rs:
- Message/MessageBuilder carry ordered `tags: Vec<(String,String)>`; `parse`
peels a leading `@key=value;...` segment (length-checked against
MAX_TAG_DATA_LEN=8191 → ParseError::TagsTooLong, unescaped per the spec table),
`to_line`/`to_wire` render it (server tags before client `+` tags, by insertion
order); `tags()`/`tag(key)` accessors; builder `.tag(k,v)`.
- to_wire caps only the body at 512; the tag prefix is separate budget per spec.
- Escaping table both ways: `;`<->`\:`, ` `<->`\s`, `\`<->`\\`, CR<->`\r`,
LF<->`\n`; on unescape `\<other>` drops the backslash, lone trailing `\` drops.
cap.rs: advertise `message-tags` in SUPPORTED; `ClientCaps.message_tags`.
This is behavior-bearing on its own: the moment the cap is advertised, tag-capable
clients send `@tag PRIVMSG ...`, and the server now parses the `@tags` segment
correctly (it would otherwise be mistaken for the command). A client-only tag is
parsed and currently dropped; TAGMSG + client-only tag relay (per-recipient cap
gating + S2S) and the 417 reply are slice 57.
Tests (TDD, inverse invariants): message.rs units (tag/body split; untagged line
byte-identical; escaping round-trip; valueless/empty-value normalization;
render->parse->render stable; TagsTooLong; body-cap-with-tag-prefix; lone/unknown
escapes). cap.rs units updated for the new LS token. Fuzzing:
message_tags_proptest.rs (render/parse round-trip, escaping round-trip,
never-panic on arbitrary @input, untagged-line no-regression). Regenerated the 2
golden_cap snapshots.
cargo test -p leveva 814 lib + proptests green; clippy clean; build warning-free.