CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(create_report): make diagnostic span non-optional so response body renders

Every `accepted_*` / `rejected_*` diagnostic in the report stage carried
the response body via `#[source_code]` paired with an `Option<SourceSpan>`
labelled "Pseudo-span over the whole body". In practice all nine call
sites passed `span: None`, and miette's `GraphicalReportHandler` silently
drops `source_code` when no `#[label]` has a concrete span. Result: the
body was attached to the diagnostic but never shown, and users saw only
the header + help text.

Fix: change each of the nine diagnostic structs (`UnauthenticatedAccepted`,
`MalformedBearerAccepted`, `WrongAudAccepted`, `WrongLxmAccepted`,
`ExpiredAccepted`, `ShapeNot400`, `SelfMintRejected`,
`PdsServiceAuthRejected`, `PdsProxiedRejected`) from
`span: Option<SourceSpan>` to `span: SourceSpan`, and return the
whole-body span alongside the `NamedSource` from the two
`body_as_named_source*` helpers. All twelve construction sites now
destructure the tuple.

The `report_all_fail_misconfigured_labeler_snapshot` regenerates with
the response body now visible under each SpecViolation row (status,
JSON body with `accepted here` / `rejected here` label pointing at the
body, and the help text).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

authored by

Jack Grigg
Claude Opus 4.7
and committed by
Tangled
ffd24f8a be6d6d8d

+149 -48
+71 -48
src/commands/test/labeler/create_report.rs
··· 762 762 } 763 763 RejectionShape::WrongStatus { status } => { 764 764 let status_u16 = status.as_u16(); 765 + let (source_code, span) = body_as_named_source(&resp); 765 766 let diag = Box::new(UnauthenticatedAccepted { 766 767 status: status_u16, 767 - source_code: body_as_named_source(&resp), 768 - span: None, 768 + source_code, 769 + span, 769 770 }); 770 771 results.push(Check::UnauthenticatedRejected.spec_violation(Some(diag))); 771 772 } ··· 794 795 } 795 796 RejectionShape::WrongStatus { status } => { 796 797 let status_u16 = status.as_u16(); 798 + let (source_code, span) = body_as_named_source(&resp); 797 799 let diag = Box::new(MalformedBearerAccepted { 798 800 status: status_u16, 799 - source_code: body_as_named_source(&resp), 800 - span: None, 801 + source_code, 802 + span, 801 803 }); 802 804 results.push(Check::MalformedBearerRejected.spec_violation(Some(diag))); 803 805 } ··· 864 866 }) 865 867 } 866 868 RejectionShape::WrongStatus { .. } => { 869 + let (source_code, span) = body_as_named_source(&resp); 867 870 let diag = Box::new(WrongAudAccepted { 868 871 status: resp.status.as_u16(), 869 - source_code: body_as_named_source(&resp), 870 - span: None, 872 + source_code, 873 + span, 871 874 }); 872 875 results.push(Check::WrongAudRejected.spec_violation(Some(diag))); 873 876 } ··· 900 903 }) 901 904 } 902 905 RejectionShape::WrongStatus { .. } => { 906 + let (source_code, span) = body_as_named_source(&resp); 903 907 let diag = Box::new(WrongLxmAccepted { 904 908 status: resp.status.as_u16(), 905 - source_code: body_as_named_source(&resp), 906 - span: None, 909 + source_code, 910 + span, 907 911 }); 908 912 results.push(Check::WrongLxmRejected.spec_violation(Some(diag))); 909 913 } ··· 937 941 }) 938 942 } 939 943 RejectionShape::WrongStatus { .. } => { 944 + let (source_code, span) = body_as_named_source(&resp); 940 945 let diag = Box::new(ExpiredAccepted { 941 946 status: resp.status.as_u16(), 942 - source_code: body_as_named_source(&resp), 943 - span: None, 947 + source_code, 948 + span, 944 949 }); 945 950 results.push(Check::ExpiredRejected.spec_violation(Some(diag))); 946 951 } ··· 983 988 || resp.status.is_server_error() 984 989 { 985 990 // AC3.6: 401 or 5xx → Advisory with shape_not_400. 991 + let (source_code, span) = body_as_named_source(&resp); 986 992 let diag = Box::new(ShapeNot400 { 987 993 status: resp.status.as_u16(), 988 994 error_name: error_name.clone(), 989 - source_code: body_as_named_source(&resp), 990 - span: None, 995 + source_code, 996 + span, 991 997 }); 992 998 results.push(Check::RejectedShapeReturns400.advisory(Some(diag))); 993 999 } else if resp.status == reqwest::StatusCode::BAD_REQUEST { 994 1000 // 400 but not `InvalidRequest` name → Advisory. 1001 + let (source_code, span) = body_as_named_source(&resp); 995 1002 let diag = Box::new(ShapeNot400 { 996 1003 status: 400, 997 1004 error_name: error_name.clone(), 998 - source_code: body_as_named_source(&resp), 999 - span: None, 1005 + source_code, 1006 + span, 1000 1007 }); 1001 1008 results.push(Check::RejectedShapeReturns400.advisory(Some(diag))); 1002 1009 } else { ··· 1004 1011 // shape is a labeler looseness issue, not the same category 1005 1012 // as the `self_mint_accepted` SpecViolation (which expects 1006 1013 // a *valid* shape to be accepted). 1014 + let (source_code, span) = body_as_named_source(&resp); 1007 1015 let diag = Box::new(ShapeNot400 { 1008 1016 status: resp.status.as_u16(), 1009 1017 error_name, 1010 - source_code: body_as_named_source(&resp), 1011 - span: None, 1018 + source_code, 1019 + span, 1012 1020 }); 1013 1021 results.push(Check::RejectedShapeReturns400.advisory(Some(diag))); 1014 1022 } ··· 1098 1106 } 1099 1107 Ok(resp) => { 1100 1108 // AC4.3: non-2xx ⇒ SpecViolation. 1109 + let (source_code, span) = body_as_named_source(&resp); 1101 1110 let diag = Box::new(SelfMintRejected { 1102 1111 status: resp.status.as_u16(), 1103 - source_code: body_as_named_source(&resp), 1104 - span: None, 1112 + source_code, 1113 + span, 1105 1114 }); 1106 1115 results.push(Check::SelfMintAccepted.spec_violation(Some(diag))); 1107 1116 } ··· 1185 1194 results.push(Check::PdsServiceAuthAccepted.pass()); 1186 1195 } 1187 1196 Ok(resp) => { 1197 + let (source_code, span) = body_as_named_source(&resp); 1188 1198 let diag = Box::new(PdsServiceAuthRejected { 1189 1199 status: resp.status.as_u16(), 1190 - source_code: body_as_named_source(&resp), 1191 - span: None, 1200 + source_code, 1201 + span, 1192 1202 }); 1193 1203 results 1194 1204 .push(Check::PdsServiceAuthAccepted.spec_violation(Some(diag))); ··· 1223 1233 || resp.status.as_u16() == 504; 1224 1234 if is_upstream_label_error { 1225 1235 // AC6.2: labeler-side rejection surfaced by PDS. 1236 + let (source_code, span) = body_as_named_source_from_pds(&resp); 1226 1237 let diag = Box::new(PdsProxiedRejected { 1227 1238 status: resp.status.as_u16(), 1228 - source_code: body_as_named_source_from_pds(&resp), 1229 - span: None, 1239 + source_code, 1240 + span, 1230 1241 }); 1231 1242 results.push(Check::PdsProxiedAccepted.spec_violation(Some(diag))); 1232 1243 } else { ··· 1418 1429 /// Response body for context. 1419 1430 #[source_code] 1420 1431 pub source_code: NamedSource<Arc<[u8]>>, 1421 - /// Pseudo-span over the whole body. 1432 + /// Span covering the response body so miette renders `source_code`. 1422 1433 #[label("accepted here")] 1423 - pub span: Option<SourceSpan>, 1434 + pub span: SourceSpan, 1424 1435 } 1425 1436 1426 1437 /// Diagnostic for AC2.4: labeler accepted a malformed bearer token. ··· 1436 1447 /// Response body for context. 1437 1448 #[source_code] 1438 1449 pub source_code: NamedSource<Arc<[u8]>>, 1439 - /// Pseudo-span over the whole body. 1450 + /// Span covering the response body so miette renders `source_code`. 1440 1451 #[label("accepted here")] 1441 - pub span: Option<SourceSpan>, 1452 + pub span: SourceSpan, 1442 1453 } 1443 1454 1444 1455 /// Diagnostic for AC3.2: labeler accepted JWT with wrong `aud` claim. ··· 1454 1465 /// Response body for context. 1455 1466 #[source_code] 1456 1467 pub source_code: NamedSource<Arc<[u8]>>, 1457 - /// Pseudo-span over the whole body. 1468 + /// Span covering the response body so miette renders `source_code`. 1458 1469 #[label("accepted here")] 1459 - pub span: Option<SourceSpan>, 1470 + pub span: SourceSpan, 1460 1471 } 1461 1472 1462 1473 /// Diagnostic for AC3.3: labeler accepted JWT with wrong `lxm` claim. ··· 1472 1483 /// Response body for context. 1473 1484 #[source_code] 1474 1485 pub source_code: NamedSource<Arc<[u8]>>, 1475 - /// Pseudo-span over the whole body. 1486 + /// Span covering the response body so miette renders `source_code`. 1476 1487 #[label("accepted here")] 1477 - pub span: Option<SourceSpan>, 1488 + pub span: SourceSpan, 1478 1489 } 1479 1490 1480 1491 /// Diagnostic for AC3.4: labeler accepted expired JWT. ··· 1490 1501 /// Response body for context. 1491 1502 #[source_code] 1492 1503 pub source_code: NamedSource<Arc<[u8]>>, 1493 - /// Pseudo-span over the whole body. 1504 + /// Span covering the response body so miette renders `source_code`. 1494 1505 #[label("accepted here")] 1495 - pub span: Option<SourceSpan>, 1506 + pub span: SourceSpan, 1496 1507 } 1497 1508 1498 1509 /// Diagnostic for AC3.6: labeler rejected invalid shape with wrong status. ··· 1510 1521 /// Response body for context. 1511 1522 #[source_code] 1512 1523 pub source_code: NamedSource<Arc<[u8]>>, 1513 - /// Pseudo-span over the whole body. 1524 + /// Span covering the response body so miette renders `source_code`. 1514 1525 #[label("rejected with wrong status here")] 1515 - pub span: Option<SourceSpan>, 1526 + pub span: SourceSpan, 1516 1527 } 1517 1528 1518 1529 /// Diagnostic for AC4.3: self-mint report rejected by the labeler. ··· 1528 1539 /// Response body for context. 1529 1540 #[source_code] 1530 1541 pub source_code: NamedSource<Arc<[u8]>>, 1531 - /// Pseudo-span over the whole body. 1542 + /// Span covering the response body so miette renders `source_code`. 1532 1543 #[label("rejected here")] 1533 - pub span: Option<SourceSpan>, 1544 + pub span: SourceSpan, 1534 1545 } 1535 1546 1536 1547 /// Diagnostic for AC5.2: labeler rejected PDS-minted service-auth JWT. ··· 1546 1557 /// Response body for context. 1547 1558 #[source_code] 1548 1559 pub source_code: NamedSource<Arc<[u8]>>, 1549 - /// Pseudo-span over the whole body. 1560 + /// Span covering the response body so miette renders `source_code`. 1550 1561 #[label("rejected here")] 1551 - pub span: Option<SourceSpan>, 1562 + pub span: SourceSpan, 1552 1563 } 1553 1564 1554 1565 /// Diagnostic for AC6.2: labeler rejected PDS-proxied createReport. ··· 1564 1575 /// Response body for context. 1565 1576 #[source_code] 1566 1577 pub source_code: NamedSource<Arc<[u8]>>, 1567 - /// Pseudo-span over the whole body. 1578 + /// Span covering the response body so miette renders `source_code`. 1568 1579 #[label("rejected here")] 1569 - pub span: Option<SourceSpan>, 1580 + pub span: SourceSpan, 1570 1581 } 1571 1582 1572 - /// Construct a `NamedSource` from the pretty-printed response body. 1573 - /// Used for every `accepted_*` diagnostic here. 1574 - pub(crate) fn body_as_named_source(resp: &RawCreateReportResponse) -> NamedSource<Arc<[u8]>> { 1583 + /// Construct a `NamedSource` and a span covering the whole body. 1584 + /// 1585 + /// The span must be non-empty (and present on a `#[label]` field) for miette's 1586 + /// `GraphicalReportHandler` to actually render the `source_code` block; a 1587 + /// `None` span causes the source to be silently dropped from the rendered 1588 + /// diagnostic. We therefore return the span alongside the source and expect 1589 + /// every `accepted_*` / `rejected_*` diagnostic to wire both through. 1590 + pub(crate) fn body_as_named_source( 1591 + resp: &RawCreateReportResponse, 1592 + ) -> (NamedSource<Arc<[u8]>>, SourceSpan) { 1575 1593 let pretty = pretty_json_for_display(&resp.raw_body); 1576 - NamedSource::new(resp.source_url.clone(), pretty) 1594 + let span = SourceSpan::new(0.into(), pretty.len()); 1595 + (NamedSource::new(resp.source_url.clone(), pretty), span) 1577 1596 } 1578 1597 1579 - /// Construct a `NamedSource` from the pretty-printed response body from the PDS. 1580 - /// Used for PDS-mediated mode diagnostics where the response comes from the PDS not the labeler. 1581 - pub(crate) fn body_as_named_source_from_pds(resp: &RawPdsXrpcResponse) -> NamedSource<Arc<[u8]>> { 1598 + /// Construct a `NamedSource` and whole-body span from the PDS. Used for 1599 + /// PDS-mediated mode diagnostics where the response comes from the PDS not 1600 + /// the labeler. 1601 + pub(crate) fn body_as_named_source_from_pds( 1602 + resp: &RawPdsXrpcResponse, 1603 + ) -> (NamedSource<Arc<[u8]>>, SourceSpan) { 1582 1604 let pretty = pretty_json_for_display(&resp.raw_body); 1583 - NamedSource::new(resp.source_url.clone(), pretty) 1605 + let span = SourceSpan::new(0.into(), pretty.len()); 1606 + (NamedSource::new(resp.source_url.clone(), pretty), span) 1584 1607 } 1585 1608 1586 1609 /// Build a minimal, plausible createReport body for negative tests.
+78
tests/snapshots/labeler_report__report_all_fail_misconfigured_labeler_snapshot.snap
··· 14 14 labeler::report::unauthenticated_accepted 15 15 16 16 × Labeler accepted unauthenticated createReport (status 200) 17 + ╭─[https://labeler.test/xrpc/com.atproto.moderation.createReport:1:1] 18 + 1 │ ╭─▶ { 19 + 2 │ │ "createdAt": "2026-04-17T00:00:00.000Z", 20 + 3 │ │ "id": 1, 21 + 4 │ │ "reasonType": "com.atproto.moderation.defs#reasonOther", 22 + 5 │ │ "reportedBy": "did:web:127.0.0.1%3A0", 23 + 6 │ │ "subject": { 24 + 7 │ │ "$type": "com.atproto.admin.defs#repoRef", 25 + 8 │ │ "did": "did:plc:aaa22222222222222222bbbbbb" 26 + 9 │ │ } 27 + 10 │ ├─▶ } 28 + · ╰──── accepted here 29 + ╰──── 17 30 help: A labeler must reject createReport with 401 when no Authorization header is supplied. 18 31 [FAIL] Malformed bearer accepted (should have been rejected) 19 32 labeler::report::malformed_bearer_accepted 20 33 21 34 × Labeler accepted malformed Bearer token (status 200) 35 + ╭─[https://labeler.test/xrpc/com.atproto.moderation.createReport:1:1] 36 + 1 │ ╭─▶ { 37 + 2 │ │ "createdAt": "2026-04-17T00:00:00.000Z", 38 + 3 │ │ "id": 1, 39 + 4 │ │ "reasonType": "com.atproto.moderation.defs#reasonOther", 40 + 5 │ │ "reportedBy": "did:web:127.0.0.1%3A0", 41 + 6 │ │ "subject": { 42 + 7 │ │ "$type": "com.atproto.admin.defs#repoRef", 43 + 8 │ │ "did": "did:plc:aaa22222222222222222bbbbbb" 44 + 9 │ │ } 45 + 10 │ ├─▶ } 46 + · ╰──── accepted here 47 + ╰──── 22 48 help: A labeler must reject createReport with 401 when the Authorization header carries a non-JWT string. 23 49 [FAIL] JWT with wrong `aud` accepted 24 50 labeler::report::wrong_aud_accepted 25 51 26 52 × Labeler accepted JWT with wrong `aud` (status 200) 53 + ╭─[https://labeler.test/xrpc/com.atproto.moderation.createReport:1:1] 54 + 1 │ ╭─▶ { 55 + 2 │ │ "createdAt": "2026-04-17T00:00:00.000Z", 56 + 3 │ │ "id": 1, 57 + 4 │ │ "reasonType": "com.atproto.moderation.defs#reasonOther", 58 + 5 │ │ "reportedBy": "did:web:127.0.0.1%3A0", 59 + 6 │ │ "subject": { 60 + 7 │ │ "$type": "com.atproto.admin.defs#repoRef", 61 + 8 │ │ "did": "did:plc:aaa22222222222222222bbbbbb" 62 + 9 │ │ } 63 + 10 │ ├─▶ } 64 + · ╰──── accepted here 65 + ╰──── 27 66 help: A labeler must reject JWTs whose `aud` claim does not match its own DID. 28 67 [FAIL] JWT with wrong `lxm` accepted 29 68 labeler::report::wrong_lxm_accepted 30 69 31 70 × Labeler accepted JWT with wrong `lxm` (status 200) 71 + ╭─[https://labeler.test/xrpc/com.atproto.moderation.createReport:1:1] 72 + 1 │ ╭─▶ { 73 + 2 │ │ "createdAt": "2026-04-17T00:00:00.000Z", 74 + 3 │ │ "id": 1, 75 + 4 │ │ "reasonType": "com.atproto.moderation.defs#reasonOther", 76 + 5 │ │ "reportedBy": "did:web:127.0.0.1%3A0", 77 + 6 │ │ "subject": { 78 + 7 │ │ "$type": "com.atproto.admin.defs#repoRef", 79 + 8 │ │ "did": "did:plc:aaa22222222222222222bbbbbb" 80 + 9 │ │ } 81 + 10 │ ├─▶ } 82 + · ╰──── accepted here 83 + ╰──── 32 84 help: A labeler must reject JWTs whose `lxm` claim does not match the invoked Lexicon method. 33 85 [FAIL] Expired JWT accepted 34 86 labeler::report::expired_accepted 35 87 36 88 × Labeler accepted expired JWT (status 200) 89 + ╭─[https://labeler.test/xrpc/com.atproto.moderation.createReport:1:1] 90 + 1 │ ╭─▶ { 91 + 2 │ │ "createdAt": "2026-04-17T00:00:00.000Z", 92 + 3 │ │ "id": 1, 93 + 4 │ │ "reasonType": "com.atproto.moderation.defs#reasonOther", 94 + 5 │ │ "reportedBy": "did:web:127.0.0.1%3A0", 95 + 6 │ │ "subject": { 96 + 7 │ │ "$type": "com.atproto.admin.defs#repoRef", 97 + 8 │ │ "did": "did:plc:aaa22222222222222222bbbbbb" 98 + 9 │ │ } 99 + 10 │ ├─▶ } 100 + · ╰──── accepted here 101 + ╰──── 37 102 help: A labeler must reject JWTs whose `exp` claim is in the past. 38 103 [WARN] Rejection status was not 400 InvalidRequest 39 104 labeler::report::shape_not_400 40 105 41 106 × Unadvertised `reasonType` was rejected with status 200, expected 400 InvalidRequest 107 + ╭─[https://labeler.test/xrpc/com.atproto.moderation.createReport:1:1] 108 + 1 │ ╭─▶ { 109 + 2 │ │ "createdAt": "2026-04-17T00:00:00.000Z", 110 + 3 │ │ "id": 1, 111 + 4 │ │ "reasonType": "com.atproto.moderation.defs#reasonOther", 112 + 5 │ │ "reportedBy": "did:web:127.0.0.1%3A0", 113 + 6 │ │ "subject": { 114 + 7 │ │ "$type": "com.atproto.admin.defs#repoRef", 115 + 8 │ │ "did": "did:plc:aaa22222222222222222bbbbbb" 116 + 9 │ │ } 117 + 10 │ ├─▶ } 118 + · ╰──── rejected with wrong status here 119 + ╰──── 42 120 help: A labeler should return 400 InvalidRequest (not 401 or 500) for a `reasonType` not listed in its published LabelerPolicies.reasonTypes. 43 121 [OK] Self-mint report accepted 44 122 [OK] PDS-minted JWT accepted