@@ -158,4 +158,90 @@ TEST(WireFormat, LegacyJsonIsNotTheSameFormatAsJson) {
158158 EXPECT_NE (w::Format::LegacyJson, w::Format::Json);
159159}
160160
161+ // ── Golden shapes, one per kind ────────────────────────────────────────────
162+ //
163+ // docs/11-machine-output.md promises that within a kindVersion fields are
164+ // added and never removed or renamed. A promise nobody can break is the thing
165+ // this whole module was written against — `xlings interface --list` declares
166+ // 20 capabilities whose outputSchema is, for all 20, only
167+ // `{"exitCode": integer}`. So the promise is enforced here: these lists are
168+ // the published key set, and removing or renaming one turns this file red.
169+ //
170+ // Adding a field does NOT turn it red, deliberately — that is the one change
171+ // the contract allows.
172+ namespace {
173+
174+ void expect_has_keys (const nlohmann::json& obj,
175+ std::initializer_list<const char *> keys,
176+ std::string_view what) {
177+ for (auto k : keys)
178+ EXPECT_TRUE (obj.contains (k))
179+ << what << " lost published key '" << k
180+ << " '. Removing or renaming one is a breaking change: bump the "
181+ " kind's version in mcpp.wire and say so in "
182+ " docs/11-machine-output.md." ;
183+ }
184+
185+ } // namespace
186+
187+ TEST (WireGolden, EnvelopeKeySet) {
188+ auto j = w::to_json (w::Envelope{.kind = " mcpp.env" });
189+ expect_has_keys (j, {" schemaVersion" , " kind" , " kindVersion" , " effects" ,
190+ " mcpp" , " data" , " diagnostics" }, " the envelope" );
191+ expect_has_keys (j[" mcpp" ], {" version" , " protocol" }, " envelope.mcpp" );
192+ expect_has_keys (j[" mcpp" ][" protocol" ], {" min" , " max" }, " envelope.mcpp.protocol" );
193+ }
194+
195+ TEST (WireGolden, DiagnosticKeySet) {
196+ auto j = w::to_json (w::Envelope{
197+ .kind = " mcpp.env" ,
198+ .diagnostics = {{.code = " C" , .message = " m" , .path = " p" ,
199+ .range = w::Range{{1 , 1 }, {1 , 2 }}}}});
200+ expect_has_keys (j[" diagnostics" ][0 ],
201+ {" code" , " severity" , " source" , " message" , " path" , " range" },
202+ " a diagnostic" );
203+ expect_has_keys (j[" diagnostics" ][0 ][" range" ], {" start" , " end" }, " a range" );
204+ expect_has_keys (j[" diagnostics" ][0 ][" range" ][" start" ], {" line" , " column" },
205+ " a position" );
206+ }
207+
208+ TEST (WireGolden, ProtocolDocumentKeySet) {
209+ auto j = w::protocol_document ({{" self env" , {w::Effect::InitMcppHome}}});
210+ expect_has_keys (j, {" schemaVersion" , " kind" , " mcpp" , " envelope" , " kinds" ,
211+ " commands" }, " the protocol document" );
212+ expect_has_keys (j[" envelope" ], {" min" , " max" }, " protocol.envelope" );
213+ expect_has_keys (j[" commands" ][" self env" ], {" effects" }, " a command entry" );
214+ }
215+
216+ // Every effect name is part of the contract: a client matches on these
217+ // strings, so renaming one silently changes what a gate lets through.
218+ TEST (WireGolden, EffectNamesAreStable) {
219+ struct { w::Effect e; const char * name; } const expected[]{
220+ {w::Effect::InitMcppHome, " init-mcpp-home" },
221+ {w::Effect::ReadProject, " read-project" },
222+ {w::Effect::WriteProject, " write-project" },
223+ {w::Effect::WriteGlobalCache, " write-global-cache" },
224+ {w::Effect::Network, " network" },
225+ {w::Effect::ExecBuildScript, " exec-build-script" },
226+ };
227+ for (auto const & x : expected)
228+ EXPECT_EQ (w::effect_name (x.e ), x.name )
229+ << " effect names are matched by clients; renaming one changes "
230+ " what an untrusted-workspace gate admits" ;
231+ }
232+
233+ TEST (WireGolden, SeverityNamesAreStable) {
234+ EXPECT_EQ (w::severity_name (w::Severity::Error), " error" );
235+ EXPECT_EQ (w::severity_name (w::Severity::Warning), " warning" );
236+ EXPECT_EQ (w::severity_name (w::Severity::Note), " note" );
237+ }
238+
239+ // The kinds this build claims to speak. A client reads this list to decide
240+ // whether to bother calling; dropping one silently is a breaking change.
241+ TEST (WireGolden, DeclaredKinds) {
242+ auto j = w::protocol_document ({});
243+ expect_has_keys (j[" kinds" ], {" mcpp.env" , " mcpp.xpkg" , " mcpp.cache" },
244+ " the kind list" );
245+ }
246+
161247} // namespace
0 commit comments