1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! What the envelope needs to know about a harness's session.
//!
//! The envelope's job is to turn a resolved session identity into the
//! `X-Tapes-*` header set. To do that it needs six things — a harness id, a
//! session id, and four optional fields — and it needs them from *every*
//! harness, present and future.
//!
//! Before this trait it got them by naming one: the producer imported Claude's
//! session-file struct and read its fields directly. That is what made the
//! envelope un-shareable. It sat in the crate that declares harnesses, so
//! adding a harness could change it, and the harness registry could not take
//! its ids from the envelope without the two crates depending on each other.
//!
//! [`HarnessSession`] states the requirement instead of importing a supplier of
//! it. A harness crate implements it for whatever shape it already parses — a
//! foreign trait on a local type, which is always allowed — and the envelope
//! constructs from `&impl HarnessSession`, naming nobody. The next harness
//! implements the same trait without a line changing here.
//!
//! # Absence is a first-class answer
//!
//! Every field but the two required ones defaults to "this harness has no such
//! thing". A harness that never names a session, or ships no version string,
//! implements nothing extra and the corresponding header is simply omitted —
//! which is the envelope's existing meaning for an absent optional (see
//! `X-Tapes-*` field docs: absent and empty stay distinguishable downstream).
//! Nothing is ever filled with a placeholder to satisfy the shape.
/// A harness session, as the envelope producer sees it.
///
/// Implement this on the type a harness crate already parses out of whatever
/// the harness publishes — a session file, a rollout record, a lifecycle
/// report. The methods are a projection, not a parser: they hand back what the
/// implementor already holds.
///
/// Only [`harness_id`](Self::harness_id) and [`session_id`](Self::session_id)
/// are required, because an envelope without them is not an identity at all —
/// the producer's completeness rule rejects exactly that pair being absent.
/// Everything else defaults to absent.